Uses gift package for server side resizing of selected letterhead file. It should fit an image file with aspect ratio of 2:1 and be a reasonable size. A 400x200 resizeToFit is used for now. gift just uses the native image/draw package internally. https://pkg.go.dev/image/drawmaster
@@ -16,10 +16,10 @@ | |||||
<h3>Letterhead</h3> | <h3>Letterhead</h3> | ||||
<canvas class="displayer" height="200" ref="letterhead"></canvas> | <canvas class="displayer" height="200" ref="letterhead"></canvas> | ||||
<input type="file" | <input type="file" | ||||
@change="e => changeLetterhead(e.target.files[0])" | |||||
@change="e => {setLetterhead(e.target.files[0])}" | |||||
/> | /> | ||||
<button @click="uploadLetterhead">Upload</button> | <button @click="uploadLetterhead">Upload</button> | ||||
<label class="error">{{avatarError}}</label> | |||||
<label class="error">{{letterheadError}}</label> | |||||
</section> | </section> | ||||
<section class="form inputs special"> | <section class="form inputs special"> | ||||
@@ -68,6 +68,7 @@ let letterhead = ref(null) // the canvas element | |||||
let ready = ref(false) | let ready = ref(false) | ||||
let avatarChanged = ref(false) | let avatarChanged = ref(false) | ||||
let avatarError = ref('') | let avatarError = ref('') | ||||
let letterHeadError = ref('') | |||||
let letterheadError = ref('') | let letterheadError = ref('') | ||||
const props = defineProps(['user', 'token']) | const props = defineProps(['user', 'token']) | ||||
const emit = defineEmits(['updateAvatar', 'updateLetterhead']) | const emit = defineEmits(['updateAvatar', 'updateLetterhead']) | ||||
@@ -109,6 +110,28 @@ function uploadLetterhead() { | |||||
}) | }) | ||||
} | } | ||||
function setLetterhead(f) { | |||||
const validTypes = ['image/jpeg', 'image/png'] | |||||
if (!validTypes.includes(f.type)) { | |||||
letterheadError.value = 'Image must be JPEG of PNG format' | |||||
return | |||||
} | |||||
fetch(`/api/letterhead`, | |||||
{method: 'POST', | |||||
body: f, | |||||
headers: { | |||||
"Accept": "application/json", | |||||
"Authorization": `Bearer ${props.token}`, | |||||
}, | |||||
}).then(resp => { | |||||
if (resp.ok) { | |||||
resp.blob().then(b => changeLetterhead(b)) | |||||
} else { | |||||
resp.text().then(e => letterheadError.value = e) | |||||
} | |||||
}) | |||||
} | |||||
function changeAvatar(blob) { | function changeAvatar(blob) { | ||||
const validTypes = ['image/jpeg', 'image/png'] | const validTypes = ['image/jpeg', 'image/png'] | ||||
@@ -135,13 +158,13 @@ function changeLetterhead(blob) { | |||||
return | return | ||||
} | } | ||||
letterheadError.value = '' | |||||
return createImageBitmap(blob, | |||||
{resizeWidth: 400, resizeHeight: 200, resizeQuality: 'medium'}). | |||||
createImageBitmap(blob). | |||||
then((img) => { | then((img) => { | ||||
letterhead.value.getContext("2d").drawImage(img, 0, 0, 400, 200) | |||||
let ctx = letterhead.value.getContext("2d") | |||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height) | |||||
ctx.drawImage(img, 0, 0) | |||||
}) | }) | ||||
} | } | ||||
watch(props.user, (u) => { | watch(props.user, (u) => { | ||||
@@ -4,6 +4,7 @@ go 1.19 | |||||
require ( | require ( | ||||
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0 | github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0 | ||||
github.com/disintegration/gift v1.2.1 | |||||
github.com/go-sql-driver/mysql v1.6.0 | github.com/go-sql-driver/mysql v1.6.0 | ||||
github.com/golang-jwt/jwt/v4 v4.5.0 | github.com/golang-jwt/jwt/v4 v4.5.0 | ||||
) | ) |
@@ -1,5 +1,7 @@ | |||||
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0 h1:DNrExYwvyyI404SxdUCCANAj9TwnGjRfa3cYFMNY1AU= | github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0 h1:DNrExYwvyyI404SxdUCCANAj9TwnGjRfa3cYFMNY1AU= | ||||
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0/go.mod h1:SQq4xfIdvf6WYKSDxAJc+xOJdolt+/bc1jnQKMtPMvQ= | github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0/go.mod h1:SQq4xfIdvf6WYKSDxAJc+xOJdolt+/bc1jnQKMtPMvQ= | ||||
github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= | |||||
github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= | |||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= | ||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | ||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= | github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= | ||||
@@ -1,9 +1,10 @@ | |||||
{ | { | ||||
"scripts": { | "scripts": { | ||||
"watch": "webpack --mode development --watch" | |||||
"watch": "webpack --mode development --watch" | |||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
"vue": "^3.2.41" | |||||
"vue": "^3.2.41", | |||||
"vue-html2pdf": "^1.8.0" | |||||
}, | }, | ||||
"devDependencies": { | "devDependencies": { | ||||
"css-loader": "^6.7.1", | "css-loader": "^6.7.1", | ||||
@@ -20,6 +20,10 @@ import ( | |||||
"io" | "io" | ||||
// pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf" | // pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf" | ||||
"github.com/golang-jwt/jwt/v4" | "github.com/golang-jwt/jwt/v4" | ||||
"github.com/disintegration/gift" | |||||
"image" | |||||
"image/png" | |||||
_ "image/jpeg" | |||||
) | ) | ||||
type User struct { | type User struct { | ||||
@@ -1566,12 +1570,13 @@ func checkFHA(l Loan, b Borrower) error { | |||||
return nil | return nil | ||||
} | } | ||||
// Loan option for veterans with no set rules | |||||
// Loan option for veterans with no set rules. Mainly placeholder. | |||||
func checkVA(l Loan, b Borrower) error { | func checkVA(l Loan, b Borrower) error { | ||||
return nil | return nil | ||||
} | } | ||||
// Loan option for residents of rural areas with no set rules | |||||
// Loan option for residents of rural areas with no set rules. | |||||
// Mainly placeholder. | |||||
func checkUSDA(l Loan, b Borrower) error { | func checkUSDA(l Loan, b Borrower) error { | ||||
return nil | return nil | ||||
} | } | ||||
@@ -1677,6 +1682,31 @@ func showPDF(w http.ResponseWriter, r *http.Request) { | |||||
} | } | ||||
func clipLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||||
var validTypes []string = []string{"image/png", "image/jpeg"} | |||||
var isValidType bool | |||||
var err error | |||||
// claims, err := getClaims(r) | |||||
if err != nil { http.Error(w, "Invalid token.", 422); return } | |||||
img, t, err := image.Decode(r.Body) | |||||
if err != nil { http.Error(w, "Invalid file.", 422); return } | |||||
for _, v := range validTypes { | |||||
if v == "image/"+t { isValidType = true } | |||||
} | |||||
if !isValidType { http.Error(w, "Invalid file type.", 422); return } | |||||
g := gift.New( | |||||
gift.ResizeToFit(400, 200, gift.LanczosResampling), | |||||
) | |||||
dst := image.NewRGBA(g.Bounds(img.Bounds())) | |||||
g.Draw(dst, img) | |||||
w.Header().Set("Content-Type", "image/png") | |||||
err = png.Encode(w, dst) | |||||
if err != nil { http.Error(w, "Error encoding.", 500); return } | |||||
} | |||||
func api(w http.ResponseWriter, r *http.Request) { | func api(w http.ResponseWriter, r *http.Request) { | ||||
var args []string | var args []string | ||||
@@ -1702,6 +1732,9 @@ func api(w http.ResponseWriter, r *http.Request) { | |||||
case match(p, "/api/token", &args) && | case match(p, "/api/token", &args) && | ||||
r.Method == http.MethodGet && guard(r, 1): | r.Method == http.MethodGet && guard(r, 1): | ||||
getToken(w, db, r) | getToken(w, db, r) | ||||
case match(p, "/api/letterhead", &args) && | |||||
r.Method == http.MethodPost && guard(r, 1): | |||||
clipLetterhead(w, db, r) | |||||
case match(p, "/api/users", &args) && // Array of all users | case match(p, "/api/users", &args) && // Array of all users | ||||
r.Method == http.MethodGet && guard(r, 3): | r.Method == http.MethodGet && guard(r, 3): | ||||
getUsers(w, db, r) | getUsers(w, db, r) | ||||