Also structure CLI functions to allow for `skouter dev seed` command.master
@@ -8,3 +8,6 @@ DBPass | |||||
Uses ESbuild and the Vue node package. | Uses ESbuild and the Vue node package. | ||||
`sudo apt install esbuild; npm install vue;` | `sudo apt install esbuild; npm install vue;` | ||||
`` | `` | ||||
### Starting Server | |||||
It reads database info from environment variables, unless called as `skouter dev`. Dev mode uses default datbase credentials for user 'tester'. |
@@ -45,13 +45,6 @@ v-else-if="active == 4" /> | |||||
<login @login="start" /> | <login @login="start" /> | ||||
</template> | </template> | ||||
<estimate-test | |||||
v-else-if="active == 7" | |||||
:token="token" | |||||
:estimate="preview" | |||||
:user="user" | |||||
/> | |||||
</div> | </div> | ||||
</template> | </template> | ||||
@@ -61,7 +54,6 @@ import Spinner from "./spinner.vue" | |||||
import Home from "./home.vue" | import Home from "./home.vue" | ||||
import NewEstimate from "./new/new.vue" | import NewEstimate from "./new/new.vue" | ||||
import Estimates from "./estimates.vue" | import Estimates from "./estimates.vue" | ||||
import EstimateTest from "./estimate-test.vue" | |||||
import Settings from "./settings.vue" | import Settings from "./settings.vue" | ||||
import SignOut from "./sign-out.vue" | import SignOut from "./sign-out.vue" | ||||
import Login from "./login.vue" | import Login from "./login.vue" | ||||
@@ -1,137 +0,0 @@ | |||||
<template> | |||||
<div id="pdf-doc" ref="doc" v-if="estimate"> | |||||
<div class="disclaimer"><p>Actual costs may vary from estimates after approval. Get an official quote before choosing a loan.</p></div> | |||||
<header class="heading"> | |||||
<img :src="letterhead" /> | |||||
<div> | |||||
<div class="user-info"> | |||||
<h4>{{user.firstName + " " + user.lastName}}</h4> | |||||
<span>{{user.email}}</span> | |||||
<span>{{user.phone}}</span> | |||||
<small>{{user.address.street}}</small> | |||||
<small> | |||||
{{`${user.address.city}, ${user.address.region} ${user.address.zip}`}} | |||||
</small> | |||||
</div> | |||||
<img :src="avatar"/> | |||||
</div> | |||||
</header> | |||||
<button @click="getPdf">Generate</button> | |||||
<a :href="pdfLink" v-if="pdfLink" download="estimate.pdf">download </a> | |||||
</div> | |||||
</template> | |||||
<script setup> | |||||
import { ref, computed, onMounted } from "vue" | |||||
import html2pdf from "html2pdf.js"; | |||||
const doc = ref(null) | |||||
const props = defineProps(['token', 'estimate', 'user']) | |||||
const estimate = ref(null) | |||||
const estimates = ref(null) | |||||
const pdfLink = ref('') | |||||
const letterhead = computed(() => { | |||||
if (!props.user.letterhead) return null | |||||
return URL.createObjectURL(props.user.letterhead) | |||||
}) | |||||
const avatar = computed(() => { | |||||
if (!props.user.letterhead) return null | |||||
console.log(props.user) | |||||
return URL.createObjectURL(props.user.avatar) | |||||
}) | |||||
function makePDF() { | |||||
var opt = { | |||||
image: { type: 'png', quality: 1 }, | |||||
} | |||||
html2pdf(doc.value, opt) | |||||
} | |||||
function getEstimates() { | |||||
return fetch(`/api/estimates`, | |||||
{method: 'GET', | |||||
headers: { | |||||
"Accept": "application/json", | |||||
"Authorization": `Bearer ${props.token}`, | |||||
}, | |||||
}).then(response => { | |||||
if (response.ok) { return response.json() } else { | |||||
response.text().then(t => console.log(t)) | |||||
} | |||||
}).then (result => { | |||||
if (!result || !result.length) return // Exit if token is invalid or no fees are saved | |||||
estimates.value = result | |||||
// console.log(result) | |||||
}) | |||||
} | |||||
function getPdf() { | |||||
fetch(`/api/pdf`, | |||||
{method: 'POST', | |||||
body: JSON.stringify(estimate.value), | |||||
headers: { | |||||
"Accept": "application/json", | |||||
"Authorization": `Bearer ${props.token}`, | |||||
}, | |||||
}).then(response => { | |||||
if (response.ok) { return response.blob() } | |||||
else { | |||||
return null | |||||
} | |||||
}).then (result => { | |||||
if (!result) return | |||||
pdfLink.value = URL.createObjectURL(result) | |||||
}) | |||||
} | |||||
onMounted(() => { | |||||
getEstimates().then(() => estimate.value = estimates.value[0]) | |||||
}) | |||||
</script> | |||||
<style scoped> | |||||
#pdf-doc { | |||||
margin: 4px 30px; | |||||
} | |||||
.disclaimer { | |||||
font-weight: bold; | |||||
border-bottom: 1px solid lightgrey; | |||||
margin-bottom: 20px; | |||||
color: var(--text); | |||||
} | |||||
.disclaimer p { | |||||
margin: 5px 0; | |||||
} | |||||
h4 { | |||||
margin: 4px 0; | |||||
} | |||||
header.heading { | |||||
display: flex; | |||||
justify-content: space-between; | |||||
} | |||||
.user-info { | |||||
display: flex; | |||||
flex-flow: column; | |||||
} | |||||
#pdf-doc header.heading > div { | |||||
display: flex; | |||||
gap: 8px; | |||||
text-align: right; | |||||
} | |||||
</style> |
@@ -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/brianvoe/gofakeit/v6 v6.23.2 | |||||
github.com/disintegration/gift v1.2.1 | 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/brianvoe/gofakeit/v6 v6.23.2 h1:lVde18uhad5wII/f5RMVFLtdQNE0HaGFuBUXmYKk8i8= | |||||
github.com/brianvoe/gofakeit/v6 v6.23.2/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= | |||||
github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= | github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= | ||||
github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= | 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= | ||||
@@ -24,6 +24,7 @@ import ( | |||||
// 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" | "github.com/disintegration/gift" | ||||
"github.com/brianvoe/gofakeit/v6" | |||||
"image" | "image" | ||||
"image/png" | "image/png" | ||||
_ "image/jpeg" | _ "image/jpeg" | ||||
@@ -195,7 +196,7 @@ var paths = map[string]string { | |||||
var pages = map[string]Page { | var pages = map[string]Page { | ||||
"home": cache("home", "Home"), | "home": cache("home", "Home"), | ||||
"terms": cache("terms", "Terms and Conditions"), | "terms": cache("terms", "Terms and Conditions"), | ||||
"test": cachePdf("comparison"), | |||||
"report": cachePdf("comparison"), | |||||
"app": cache("app", "App"), | "app": cache("app", "App"), | ||||
} | } | ||||
@@ -1846,10 +1847,7 @@ func validateEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||||
if err != nil { http.Error(w, err.Error(), 406); return } | if err != nil { http.Error(w, err.Error(), 406); return } | ||||
} | } | ||||
func showPDF(w http.ResponseWriter, r *http.Request) { | |||||
// var args []string | |||||
// p := r.URL.Path | |||||
func checkPdf(w http.ResponseWriter, r *http.Request) { | |||||
db, err := sql.Open("mysql", | db, err := sql.Open("mysql", | ||||
fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter_dev", | fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter_dev", | ||||
os.Getenv("DBUser"), | os.Getenv("DBUser"), | ||||
@@ -1863,8 +1861,6 @@ func showPDF(w http.ResponseWriter, r *http.Request) { | |||||
// maybe os.Exit(1) instead | // maybe os.Exit(1) instead | ||||
} | } | ||||
page := cachePdf("comparison") | |||||
estimates, err := getEstimates(db, 1, 0) | estimates, err := getEstimates(db, 1, 0) | ||||
if err != nil { w.WriteHeader(500); return } | if err != nil { w.WriteHeader(500); return } | ||||
@@ -1894,7 +1890,7 @@ func showPDF(w http.ResponseWriter, r *http.Request) { | |||||
} | } | ||||
} | } | ||||
err = page.tpl.ExecuteTemplate(w, "master.tpl", info) | |||||
err = pages["report"].tpl.ExecuteTemplate(w, "master.tpl", info) | |||||
if err != nil {fmt.Println(err)} | if err != nil {fmt.Println(err)} | ||||
} | } | ||||
@@ -1944,7 +1940,7 @@ func getPdf(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||||
base64.StdEncoding.EncodeToString(letterhead) | base64.StdEncoding.EncodeToString(letterhead) | ||||
err = pages["test"].tpl.ExecuteTemplate(stdin, "master.tpl", info) | |||||
err = pages["report"].tpl.ExecuteTemplate(stdin, "master.tpl", info) | |||||
if err != nil { | if err != nil { | ||||
w.WriteHeader(500); | w.WriteHeader(500); | ||||
log.Println(err) | log.Println(err) | ||||
@@ -1961,7 +1957,6 @@ func getPdf(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||||
} | } | ||||
if err := cmd.Wait(); err != nil { | if err := cmd.Wait(); err != nil { | ||||
// w.WriteHeader(500) | |||||
log.Println(err) | log.Println(err) | ||||
return | return | ||||
} | } | ||||
@@ -2117,7 +2112,7 @@ func route(w http.ResponseWriter, r *http.Request) { | |||||
case match(p, "/app", &args): | case match(p, "/app", &args): | ||||
page = pages[ "app" ] | page = pages[ "app" ] | ||||
case match(p, "/test", &args): | case match(p, "/test", &args): | ||||
showPDF(w, r) | |||||
checkPdf(w, r) | |||||
return | return | ||||
default: | default: | ||||
http.NotFound(w, r) | http.NotFound(w, r) | ||||
@@ -2136,21 +2131,62 @@ func serve() { | |||||
log.Fatal(http.ListenAndServe(address, nil)) | log.Fatal(http.ListenAndServe(address, nil)) | ||||
} | } | ||||
func dbSeed() { | |||||
db, err := sql.Open("mysql", | |||||
fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s", | |||||
os.Getenv("DBUser"), | |||||
os.Getenv("DBPass"), | |||||
os.Getenv("DBName"), | |||||
)) | |||||
err = db.Ping() | |||||
if err != nil { | |||||
fmt.Println("Bad database configuration: %v\n", err) | |||||
panic(err) | |||||
// maybe os.Exit(1) instead | |||||
} | |||||
} | |||||
func dev(args []string) { | func dev(args []string) { | ||||
if len(args) == 0 { | if len(args) == 0 { | ||||
os.Setenv("DBName", "skouter_dev") | os.Setenv("DBName", "skouter_dev") | ||||
os.Setenv("DBUser", "tester") | os.Setenv("DBUser", "tester") | ||||
os.Setenv("DBPass", "test123") | os.Setenv("DBPass", "test123") | ||||
serve() | serve() | ||||
return | |||||
} | } | ||||
switch args[0] { | |||||
case "seed": | |||||
dbSeed() | |||||
default: | |||||
return | |||||
} | |||||
} | |||||
func check(args []string) { | |||||
os.Setenv("DBName", "skouter_dev") | |||||
os.Setenv("DBUser", "tester") | |||||
os.Setenv("DBPass", "test123") | |||||
files := http.FileServer(http.Dir("")) | |||||
http.Handle("/assets/", files) | |||||
http.HandleFunc("/", checkPdf) | |||||
log.Fatal(http.ListenAndServe(address, nil)) | |||||
} | } | ||||
func main() { | func main() { | ||||
if len(os.Args) <= 1 { | if len(os.Args) <= 1 { | ||||
serve() | serve() | ||||
return | |||||
} | } | ||||
if os.Args[1] == "dev" { | |||||
switch os.Args[1] { | |||||
case "dev": | |||||
dev(os.Args[2:]) | dev(os.Args[2:]) | ||||
} | |||||
case "check": | |||||
check(os.Args[2:]) | |||||
default: | |||||
return | |||||
} | |||||
} | } |