@@ -385,6 +385,10 @@ section.mi .row input[type=checkbox] { | |||
position: absolute; | |||
} | |||
label.error { | |||
color: var(--text-important); | |||
} | |||
section.estimates .entry { | |||
padding: 10px 3px; | |||
border-bottom: 2px solid var(--outline); | |||
@@ -26,7 +26,11 @@ v-else-if="active == 3" | |||
@removeFeeTemp="(fee) => fees = fees.filter(f => f.id != fee.id)" | |||
/> | |||
<settings :user="user" v-else-if="active == 4" /> | |||
<settings | |||
:user="user" | |||
:token="token" | |||
@updateAvatar="updateAvatar" | |||
v-else-if="active == 4" /> | |||
<sign-out :user="user" v-else-if="active == 5" /> | |||
</template> | |||
@@ -97,12 +101,36 @@ function getUser() { | |||
this.user = result[0] | |||
if (this.user.avatar) return | |||
fetch("/assets/image/empty-avatar.jpg").then(r => r.blob()). | |||
then(b => this.user.avatar = b) | |||
return getAvatar(token) | |||
}).then(b => { | |||
const validTypes = ['image/jpeg', 'image/png'] | |||
if (!validTypes.includes(b.type) || b.size <= 1) { | |||
fetch("/assets/image/empty-avatar.jpg"). | |||
then(r => r.blob()).then( a => this.user.avatar = a ) | |||
return | |||
} | |||
this.user.avatar = b | |||
}) | |||
} | |||
function getAvatar(t) { | |||
return fetch("/api/user/avatar", | |||
{method: 'GET', | |||
headers: { | |||
"Accept": "application/json", | |||
"Authorization": `Bearer ${t || this.token}`, | |||
} | |||
}).then(r => r.blob()) | |||
} | |||
function updateAvatar() { | |||
const token = getCookie("skouter") | |||
getAvatar(token).then(b => this.user.avatar = b) | |||
} | |||
function getFees() { | |||
const token = getCookie("skouter") | |||
@@ -181,6 +209,8 @@ export default { | |||
getUser, | |||
getFees, | |||
refreshToken, | |||
updateAvatar, | |||
getAvatar, | |||
}, | |||
data() { | |||
return { | |||
@@ -6,9 +6,10 @@ | |||
<h3>Avatar</h3> | |||
<canvas width="200" height="200" ref="canvas"></canvas> | |||
<input type="file" | |||
@change="e => changeAvatar(e.target.files[0])" | |||
@change="e => uploadAvatar(e.target.files[0])" | |||
/> | |||
<button>Upload</button> | |||
<label class="error">{{avatarError}}</label> | |||
</section> | |||
<section class="form inputs"> | |||
@@ -56,14 +57,16 @@ | |||
</template> | |||
<script setup> | |||
import { ref, watch } from "vue" | |||
import { ref, watch, onMounted } from "vue" | |||
import Dialog from "./dialog.vue" | |||
let avatar = ref(null) | |||
let ready = ref(false) | |||
let avatarChanged = ref(false) | |||
let avatarError = ref('') | |||
const canvas = ref(null) | |||
const props = defineProps(['user', 'token']) | |||
const emit = defineEmits(['updateAvatar']) | |||
function save() { | |||
} | |||
@@ -73,25 +76,51 @@ function check() { | |||
} | |||
function uploadAvatar(blob) { | |||
changeAvatar(blob)?.then(() => { | |||
canvas.value.toBlob(b => { | |||
fetch(`/api/user/avatar`, | |||
{method: 'POST', | |||
body: b, | |||
headers: { | |||
"Accept": "application/json", | |||
"Authorization": `Bearer ${props.token}`, | |||
}, | |||
}).then(resp => { | |||
if (resp.ok) {emit('updateAvatar')} | |||
}) | |||
}) | |||
}) | |||
canvas.value.toBlob(b => { | |||
// uploadAvatar(b) | |||
}) | |||
} | |||
function changeAvatar(blob) { | |||
const validTypes = ['image/jpeg', 'image/png'] | |||
if (!validTypes.includes(blob.type)) { | |||
avatarError.value = 'Image must be JPEG of PNG format' | |||
return | |||
} | |||
avatarError.value = '' | |||
avatar.value = blob | |||
createImageBitmap(blob, | |||
return createImageBitmap(blob, | |||
{resizeWidth: 200, resizeHeight: 200, resizeQuality: 'medium'}). | |||
then((img) => { | |||
avatar.value = img | |||
canvas.value.getContext("2d").drawImage(img, 0, 0, 200, 200) | |||
canvas.value.toBlob(b => { | |||
console.log(b) | |||
}) | |||
}) | |||
} | |||
watch(props.user, () => { | |||
if (!props.user.avatar) return | |||
changeAvatar(props.user.avatar) | |||
}) | |||
}, {immediate: true}) | |||
</script> |
@@ -17,6 +17,7 @@ import ( | |||
"errors" | |||
"strings" | |||
"math" | |||
"io" | |||
// pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf" | |||
"github.com/golang-jwt/jwt/v4" | |||
) | |||
@@ -959,6 +960,66 @@ func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||
json.NewEncoder(w).Encode(user) | |||
} | |||
func fetchAvatar(db *sql.DB, user int) ( []byte, error ) { | |||
var img []byte | |||
var query string | |||
var err error | |||
query = `SELECT | |||
avatar | |||
FROM user WHERE user.id = ? | |||
` | |||
row := db.QueryRow(query, user) | |||
err = row.Scan(&img) | |||
if err != nil { | |||
return img, err | |||
} | |||
return img, nil | |||
} | |||
func insertAvatar(db *sql.DB, user int, img []byte) error { | |||
query := `UPDATE user | |||
SET avatar = ? | |||
WHERE id = ? | |||
` | |||
_, err := db.Exec(query, img, user) | |||
if err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
func setAvatar(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||
var validTypes []string = []string{"image/png", "image/jpeg"} | |||
var isValidType bool | |||
claims, err := getClaims(r) | |||
if err != nil { http.Error(w, "Invalid token.", 422); return } | |||
img, err := io.ReadAll(r.Body) | |||
if err != nil { http.Error(w, "Invalid file.", 422); return } | |||
for _, v := range validTypes { | |||
if v == http.DetectContentType(img) { isValidType = true } | |||
} | |||
if !isValidType { http.Error(w, "Invalid file type.", 422); return } | |||
err = insertAvatar(db, claims.Id, img) | |||
if err != nil { http.Error(w, "Could not insert.", 500); return } | |||
} | |||
func getAvatar(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||
claims, err := getClaims(r) | |||
if err != nil { http.Error(w, "Invalid token.", 422); return } | |||
img, err := fetchAvatar(db, claims.Id) | |||
if err != nil { http.Error(w, "Could not retrieve.", 500); return } | |||
w.Header().Set("Content-Type", http.DetectContentType(img)) | |||
w.Write(img) | |||
} | |||
func queryBorrower(db *sql.DB, id int) ( Borrower, error ) { | |||
var borrower Borrower | |||
var query string | |||
@@ -1602,6 +1663,14 @@ func api(w http.ResponseWriter, r *http.Request) { | |||
r.Method == http.MethodDelete && | |||
guard(r, 3): | |||
deleteUser(w, db, r) | |||
case match(p, "/api/user/avatar", &args) && | |||
r.Method == http.MethodGet && | |||
guard(r, 1): | |||
getAvatar(w, db, r) | |||
case match(p, "/api/user/avatar", &args) && | |||
r.Method == http.MethodPost && | |||
guard(r, 1): | |||
setAvatar(w, db, r) | |||
case match(p, "/api/fees", &args) && | |||
r.Method == http.MethodGet && | |||
guard(r, 1): | |||