Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2 年之前
2 年之前
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749
  1. package main
  2. import (
  3. "os"
  4. "os/exec"
  5. "net/http"
  6. "net/mail"
  7. "log"
  8. "sync"
  9. "regexp"
  10. "html/template"
  11. "database/sql"
  12. _ "github.com/go-sql-driver/mysql"
  13. "fmt"
  14. "encoding/json"
  15. "encoding/base64"
  16. "strconv"
  17. "bytes"
  18. "time"
  19. "errors"
  20. "strings"
  21. "math"
  22. "io"
  23. // pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
  24. "github.com/golang-jwt/jwt/v4"
  25. "github.com/disintegration/gift"
  26. "github.com/brianvoe/gofakeit/v6"
  27. "github.com/dustin/go-humanize"
  28. "image"
  29. "image/png"
  30. _ "image/jpeg"
  31. )
  32. type Config struct {
  33. DBName string
  34. DBUsername string
  35. DBPassword string
  36. }
  37. type Address struct {
  38. Id int `json:"id"`
  39. Full string `json:"full"`
  40. Street string `json:"street"`
  41. City string `json:"city"`
  42. Region string `json:"region"`
  43. Country string `json:"country"`
  44. Zip string `json:"zip"`
  45. }
  46. type Branch struct {
  47. Id int `json:"id"`
  48. Name string `json:"name"`
  49. Type string `json:"type"`
  50. Letterhead []byte `json:"letterhead"`
  51. Num string `json:"num"`
  52. Phone string `json:"phone"`
  53. Address Address `json:"address"`
  54. }
  55. type User struct {
  56. Id int `json:"id"`
  57. Email string `json:"email"`
  58. FirstName string `json:"firstName"`
  59. LastName string `json:"lastName"`
  60. Phone string `json:"phone"`
  61. Address Address `json:"address"`
  62. Branch Branch `json:"branch"`
  63. License License `json:"license"`
  64. Status string `json:"status"`
  65. Country string `json:"country"`
  66. Title string `json:"title"`
  67. Verified bool `json:"verified"`
  68. Role string `json:"role"`
  69. Password string `json:"password,omitempty"`
  70. }
  71. type License struct {
  72. Id int `json:"id"`
  73. UserId int `json:"userId"`
  74. Type string `json:"type"`
  75. Num string `json:"num"`
  76. }
  77. type UserClaims struct {
  78. Id int `json:"id"`
  79. Role string `json:"role"`
  80. Exp string `json:"exp"`
  81. }
  82. type Page struct {
  83. tpl *template.Template
  84. Title string
  85. Name string
  86. }
  87. type Borrower struct {
  88. Id int `json:"id"`
  89. Credit int `json:"credit"`
  90. Income int `json:"income"`
  91. Num int `json:"num"`
  92. }
  93. type FeeTemplate struct {
  94. Id int `json:"id"`
  95. User int `json:"user"`
  96. Branch int `json:"branch"`
  97. Amount int `json:"amount"`
  98. Perc float32 `json:"perc"`
  99. Type string `json:"type"`
  100. Notes string `json:"notes"`
  101. Name string `json:"name"`
  102. Category string `json:"category"`
  103. Auto bool `json:"auto"`
  104. }
  105. type Fee struct {
  106. Id int `json:"id"`
  107. LoanId int `json:"loan_id"`
  108. Amount int `json:"amount"`
  109. Perc float32 `json:"perc"`
  110. Type string `json:"type"`
  111. Notes string `json:"notes"`
  112. Name string `json:"name"`
  113. Category string `json:"category"`
  114. }
  115. type LoanType struct {
  116. Id int `json:"id"`
  117. User int `json:"user"`
  118. Branch int `json:"branch"`
  119. Name string `json:"name"`
  120. }
  121. type Loan struct {
  122. Id int `json:"id"`
  123. EstimateId int `json:"estimateId"`
  124. Type LoanType `json:"type"`
  125. Amount int `json:"amount"`
  126. Amortization string `json:"amortization"`
  127. Term int `json:"term"`
  128. Ltv float32 `json:"ltv"`
  129. Dti float32 `json:"dti"`
  130. Hoi int `json:"hoi"`
  131. Hazard int `json:"hazard"`
  132. Tax int `json:"tax"`
  133. Interest float32 `json:"interest"`
  134. Mi MI `json:"mi"`
  135. Fees []Fee `json:"fees"`
  136. Credits []Fee // Fees with negative amounts for internal use
  137. Name string `json:"title"`
  138. Result Result `json:"result"`
  139. }
  140. type MI struct {
  141. Type string `json:"user"`
  142. Label string `json:"label"`
  143. Lender string `json:"lender"`
  144. Rate float32 `json:"rate"`
  145. Premium int `json:"premium"`
  146. Upfront int `json:"upfront"`
  147. Monthly bool `json:"monthly"`
  148. FiveYearTotal float32 `json:"fiveYearTotal"`
  149. InitialAllInPremium float32 `json:"initialAllInPremium"`
  150. InitialAllInRate float32 `json:"initialAllInRate"`
  151. InitialAmount float32 `json:"initialAmount"`
  152. }
  153. type Result struct {
  154. Id int `json:"id"`
  155. LoanId int `json:"loanId"`
  156. LoanPayment int `json:"loanPayment"`
  157. TotalMonthly int `json:"totalMonthly"`
  158. TotalFees int `json:"totalFees"`
  159. TotalCredits int `json:"totalCredits"`
  160. CashToClose int `json:"cashToClose"`
  161. }
  162. type Estimate struct {
  163. Id int `json:"id"`
  164. User int `json:"user"`
  165. Borrower Borrower `json:"borrower"`
  166. Transaction string `json:"transaction"`
  167. Price int `json:"price"`
  168. Property string `json:"property"`
  169. Occupancy string `json:"occupancy"`
  170. Zip string `json:"zip"`
  171. Pud bool `json:"pud"`
  172. Loans []Loan `json:"loans"`
  173. }
  174. type Report struct {
  175. Title string
  176. Name string
  177. Avatar string
  178. Letterhead string
  179. User User
  180. Estimate Estimate
  181. }
  182. type Password struct {
  183. Old string `json:"old"`
  184. New string `json:"new"`
  185. }
  186. type Endpoint func (http.ResponseWriter, *sql.DB, *http.Request)
  187. var (
  188. regexen = make(map[string]*regexp.Regexp)
  189. relock sync.Mutex
  190. address = "127.0.0.1:8001"
  191. )
  192. var paths = map[string]string {
  193. "home": "views/home.tpl",
  194. "terms": "views/terms.tpl",
  195. "app": "views/app.tpl",
  196. "comparison": "views/report/comparison.tpl",
  197. }
  198. var pages = map[string]Page {
  199. "home": cache("home", "Home"),
  200. "terms": cache("terms", "Terms and Conditions"),
  201. "report": cachePdf("comparison"),
  202. "app": cache("app", "App"),
  203. }
  204. var roles = map[string]int{
  205. "User": 1,
  206. "Manager": 2,
  207. "Admin": 3,
  208. }
  209. var propertyTypes = []string{
  210. "Single Detached",
  211. "Single Attached",
  212. "Condo Lo-rise",
  213. "Condo Hi-rise",
  214. }
  215. var feeTypes = []string{
  216. "Government",
  217. "Title",
  218. "Required",
  219. "Lender",
  220. "Other",
  221. }
  222. // Used to validate claim in JWT token body. Checks if user id is greater than
  223. // zero and time format is valid
  224. func (c UserClaims) Valid() error {
  225. if c.Id < 1 { return errors.New("Invalid id") }
  226. t, err := time.Parse(time.UnixDate, c.Exp)
  227. if err != nil { return err }
  228. if t.Before(time.Now()) { return errors.New("Token expired.") }
  229. return err
  230. }
  231. func cache(name string, title string) Page {
  232. var p = []string{"views/master.tpl", paths[name]}
  233. tpl := template.Must(template.ParseFiles(p...))
  234. return Page{tpl: tpl, Title: title, Name: name}
  235. }
  236. func cachePdf(name string) Page {
  237. // Money is stored in cents, so it must be converted to dollars in reports
  238. dollars := func(cents int) string {
  239. return humanize.Commaf(float64(cents)/100)
  240. }
  241. // For calculating down payments
  242. diff := func(a, b int) string {
  243. return humanize.Commaf(float64(b - a)/100)
  244. }
  245. sortFees := func(ftype string, fees []Fee) []Fee {
  246. result := make([]Fee, 0)
  247. for i := range fees {
  248. if fees[i].Type != ftype { continue }
  249. result = append(result, fees[i])
  250. }
  251. return result
  252. }
  253. fm := template.FuncMap{
  254. "dollars": dollars,
  255. "diff": diff,
  256. "sortFees": sortFees }
  257. var p = []string{"views/report/master.tpl",
  258. "views/report/header.tpl",
  259. "views/report/summary.tpl",
  260. "views/report/comparison.tpl"}
  261. tpl := template.Must(template.New("master.tpl").Funcs(fm).ParseFiles(p...))
  262. return Page{ tpl: tpl, Title: "", Name: name }
  263. }
  264. func (page Page) Render(w http.ResponseWriter) {
  265. err := page.tpl.Execute(w, page)
  266. if err != nil {
  267. log.Print(err)
  268. }
  269. }
  270. func match(path, pattern string, args *[]string) bool {
  271. relock.Lock()
  272. defer relock.Unlock()
  273. regex := regexen[pattern]
  274. if regex == nil {
  275. regex = regexp.MustCompile("^" + pattern + "$")
  276. regexen[pattern] = regex
  277. }
  278. matches := regex.FindStringSubmatch(path)
  279. if len(matches) <= 0 {
  280. return false
  281. }
  282. *args = matches[1:]
  283. return true
  284. }
  285. func (estimate *Estimate) makeResults() []Result {
  286. var results []Result
  287. amortize := func(principle float64, rate float64, periods float64) int {
  288. exp := math.Pow(1+rate, periods)
  289. return int(principle * rate * exp / (exp - 1))
  290. }
  291. for i := range estimate.Loans {
  292. var loan = &estimate.Loans[i]
  293. var result Result = Result{}
  294. // Monthly payments use amortized loan payment formula plus monthly MI,
  295. // plus all other recurring fees
  296. result.LoanPayment = amortize(float64(loan.Amount),
  297. float64(loan.Interest / 100 / 12),
  298. float64(loan.Term * 12))
  299. result.TotalMonthly = result.LoanPayment + loan.Hoi + loan.Tax + loan.Hazard
  300. if loan.Mi.Monthly {
  301. result.TotalMonthly = result.TotalMonthly +
  302. int(loan.Mi.Rate/100/12*float32(loan.Amount))
  303. } else {
  304. loan.Mi.Upfront = int(loan.Mi.Rate/100*float32(loan.Amount))
  305. }
  306. for i := range loan.Fees {
  307. if loan.Fees[i].Amount > 0 {
  308. result.TotalFees = result.TotalFees + loan.Fees[i].Amount
  309. } else {
  310. result.TotalCredits = result.TotalCredits + loan.Fees[i].Amount
  311. }
  312. }
  313. result.CashToClose =
  314. result.TotalFees + result.TotalCredits + (estimate.Price - loan.Amount)
  315. result.LoanId = loan.Id
  316. loan.Result = result
  317. results = append(results, result)
  318. }
  319. return results
  320. }
  321. func summarize(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  322. var estimate Estimate
  323. err := json.NewDecoder(r.Body).Decode(&estimate)
  324. if err != nil { http.Error(w, "Invalid estimate.", 422); return }
  325. estimate.makeResults()
  326. json.NewEncoder(w).Encode(estimate)
  327. }
  328. func getLoanType( db *sql.DB, id int) (LoanType, error) {
  329. types, err := getLoanTypes(db, id, 0, 0)
  330. if err != nil { return LoanType{Id: id}, err }
  331. if len(types) == 0 {
  332. return LoanType{Id: id}, errors.New("No type with that id")
  333. }
  334. return types[0], nil
  335. }
  336. func getLoanTypes( db *sql.DB, id int, user int, branch int ) (
  337. []LoanType, error) {
  338. var loans []LoanType
  339. var query = `SELECT
  340. id,
  341. coalesce(user_id, 0),
  342. coalesce(branch_id, 0),
  343. name
  344. FROM loan_type WHERE loan_type.id = CASE @e := ? WHEN 0 THEN id ELSE @e END
  345. OR
  346. loan_type.user_id = CASE @e := ? WHEN 0 THEN id ELSE @e END
  347. OR
  348. loan_type.branch_id = CASE @e := ? WHEN 0 THEN id ELSE @e END`
  349. // Should be changed to specify user
  350. rows, err := db.Query(query, id, user, branch)
  351. if err != nil {
  352. return nil, fmt.Errorf("loan_type error: %v", err)
  353. }
  354. defer rows.Close()
  355. for rows.Next() {
  356. var loan LoanType
  357. if err := rows.Scan(
  358. &loan.Id,
  359. &loan.User,
  360. &loan.Branch,
  361. &loan.Name)
  362. err != nil {
  363. log.Printf("Error occured fetching loan: %v", err)
  364. return nil, fmt.Errorf("Error occured fetching loan: %v", err)
  365. }
  366. loans = append(loans, loan)
  367. }
  368. return loans, nil
  369. }
  370. func getFees(db *sql.DB, loan int) ([]Fee, error) {
  371. var fees []Fee
  372. query := `SELECT id, loan_id, amount, perc, type, notes, name, category
  373. FROM fee
  374. WHERE loan_id = ?`
  375. rows, err := db.Query(query, loan)
  376. if err != nil {
  377. return nil, fmt.Errorf("Fee query error %v", err)
  378. }
  379. defer rows.Close()
  380. for rows.Next() {
  381. var fee Fee
  382. if err := rows.Scan(
  383. &fee.Id,
  384. &fee.LoanId,
  385. &fee.Amount,
  386. &fee.Perc,
  387. &fee.Type,
  388. &fee.Notes,
  389. &fee.Name,
  390. &fee.Category,
  391. )
  392. err != nil {
  393. return nil, fmt.Errorf("Fees scanning error: %v", err)
  394. }
  395. fees = append(fees, fee)
  396. }
  397. return fees, nil
  398. }
  399. func fetchFeesTemp(db *sql.DB, user int, branch int) ([]FeeTemplate, error) {
  400. var fees []FeeTemplate
  401. query := `SELECT
  402. id, user_id, COALESCE(branch_id, 0), amount, perc, type, notes, name,
  403. category, auto
  404. FROM fee_template
  405. WHERE user_id = ? OR branch_id = ?
  406. `
  407. rows, err := db.Query(query, user, branch)
  408. if err != nil {
  409. return nil, fmt.Errorf("Fee template query error %v", err)
  410. }
  411. defer rows.Close()
  412. for rows.Next() {
  413. var fee FeeTemplate
  414. if err := rows.Scan(
  415. &fee.Id,
  416. &fee.User,
  417. &fee.Branch,
  418. &fee.Amount,
  419. &fee.Perc,
  420. &fee.Type,
  421. &fee.Notes,
  422. &fee.Name,
  423. &fee.Category,
  424. &fee.Auto)
  425. err != nil {
  426. return nil, fmt.Errorf("FeesTemplate scanning error: %v", err)
  427. }
  428. fees = append(fees, fee)
  429. }
  430. return fees, nil
  431. }
  432. // Fetch fees from the database
  433. func getFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  434. var fees []FeeTemplate
  435. claims, err := getClaims(r)
  436. if err != nil { w.WriteHeader(500); return }
  437. user, err := queryUser(db, claims.Id)
  438. if err != nil { w.WriteHeader(422); return }
  439. fees, err = fetchFeesTemp(db, claims.Id, user.Branch.Id)
  440. json.NewEncoder(w).Encode(fees)
  441. }
  442. // Fetch fees from the database
  443. func createFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  444. var fee FeeTemplate
  445. var query string
  446. var row *sql.Row
  447. var err error
  448. claims, err := getClaims(r)
  449. // var id int // Inserted estimate's id
  450. err = json.NewDecoder(r.Body).Decode(&fee)
  451. if err != nil { w.WriteHeader(422); return }
  452. query = `INSERT INTO fee_template
  453. (
  454. user_id,
  455. branch_id,
  456. amount,
  457. perc,
  458. type,
  459. notes,
  460. name,
  461. auto
  462. )
  463. VALUES (?, NULL, ?, ?, ?, ?, ?, ?)
  464. RETURNING id
  465. `
  466. row = db.QueryRow(query,
  467. claims.Id,
  468. fee.Amount,
  469. fee.Perc,
  470. fee.Type,
  471. fee.Notes,
  472. fee.Name,
  473. fee.Auto,
  474. )
  475. err = row.Scan(&fee.Id)
  476. if err != nil { w.WriteHeader(500); return }
  477. json.NewEncoder(w).Encode(fee)
  478. }
  479. // Fetch fees from the database
  480. func deleteFeeTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  481. var fee FeeTemplate
  482. var query string
  483. var err error
  484. // claims, err := getClaims(r)
  485. // var id int // Inserted estimate's id
  486. err = json.NewDecoder(r.Body).Decode(&fee)
  487. if err != nil { w.WriteHeader(422); return }
  488. query = `DELETE FROM fee_template WHERE id = ?`
  489. _, err = db.Exec(query, fee.Id)
  490. if err != nil { w.WriteHeader(500); return }
  491. }
  492. func getMi(db *sql.DB, loan int) (MI, error) {
  493. var mi MI
  494. query := `SELECT
  495. type, label, lender, rate, premium, upfront, five_year_total,
  496. initial_premium, initial_rate, initial_amount
  497. FROM mi WHERE loan_id = ?`
  498. rows, err := db.Query(query, loan)
  499. if err != nil { return mi, err }
  500. defer rows.Close()
  501. if (!rows.Next()) { return mi, nil }
  502. if err := rows.Scan(
  503. &mi.Type,
  504. &mi.Label,
  505. &mi.Lender,
  506. &mi.Rate,
  507. &mi.Premium,
  508. &mi.Upfront,
  509. &mi.FiveYearTotal,
  510. &mi.InitialAllInPremium,
  511. &mi.InitialAllInRate,
  512. &mi.InitialAmount,
  513. )
  514. err != nil {
  515. return mi, err
  516. }
  517. return mi, nil
  518. }
  519. func getBorrower(db *sql.DB, id int) (Borrower, error) {
  520. var borrower Borrower
  521. row := db.QueryRow(
  522. "SELECT * FROM borrower " +
  523. "WHERE id = ? LIMIT 1",
  524. id)
  525. if err := row.Scan(
  526. &borrower.Id,
  527. &borrower.Credit,
  528. &borrower.Income,
  529. &borrower.Num,
  530. )
  531. err != nil {
  532. return borrower, fmt.Errorf("Borrower scanning error: %v", err)
  533. }
  534. return borrower, nil
  535. }
  536. // Query Lender APIs and parse responses into MI structs
  537. func fetchMi(db *sql.DB, estimate *Estimate, pos int) []MI {
  538. var loan Loan = estimate.Loans[pos]
  539. var ltv = func(l float32) string {
  540. switch {
  541. case l > 95: return "LTV97"
  542. case l > 90: return "LTV95"
  543. case l > 85: return "LTV90"
  544. default: return "LTV85"
  545. }
  546. }
  547. var term = func(t int) string {
  548. switch {
  549. case t <= 10: return "A10"
  550. case t <= 15: return "A15"
  551. case t <= 20: return "A20"
  552. case t <= 25: return "A25"
  553. case t <= 30: return "A30"
  554. default: return "A40"
  555. }
  556. }
  557. var propertyCodes = map[string]string {
  558. "Single Attached": "SFO",
  559. "Single Detached": "SFO",
  560. "Condo Lo-rise": "CON",
  561. "Condo Hi-rise": "CON",
  562. }
  563. var purposeCodes = map[string]string {
  564. "Purchase": "PUR",
  565. "Refinance": "RRT",
  566. }
  567. body, err := json.Marshal(map[string]any{
  568. "zipCode": estimate.Zip,
  569. "stateCode": "CA",
  570. "address": "",
  571. "propertyTypeCode": propertyCodes[estimate.Property],
  572. "occupancyTypeCode": "PRS",
  573. "loanPurposeCode": purposeCodes[estimate.Transaction],
  574. "loanAmount": loan.Amount,
  575. "loanToValue": ltv(loan.Ltv),
  576. "amortizationTerm": term(loan.Term),
  577. "loanTypeCode": "FXD",
  578. "duLpDecisionCode": "DAE",
  579. "loanProgramCodes": []any{},
  580. "debtToIncome": loan.Dti,
  581. "wholesaleLoan": 0,
  582. "coveragePercentageCode": "L30",
  583. "productCode": "BPM",
  584. "renewalTypeCode": "CON",
  585. "numberOfBorrowers": 1,
  586. "coBorrowerCreditScores": []any{},
  587. "borrowerCreditScore": strconv.Itoa(estimate.Borrower.Credit),
  588. "masterPolicy": nil,
  589. "selfEmployedIndicator": false,
  590. "armType": "",
  591. "userId": 44504,
  592. })
  593. /*
  594. if err != nil {
  595. log.Printf("Could not marshal NationalMI body: \n%v\n%v\n",
  596. bytes.NewBuffer(body), err)
  597. func queryAddress(db *sql.DB, id int) ( Address, error ) {
  598. var address Address = Address{Id: id}
  599. var err error
  600. row := db.QueryRow(
  601. `SELECT id, full_address, street, city, region, country, zip
  602. FROM address WHERE id = ?`, id)
  603. err = row.Scan(
  604. &address.Id,
  605. &address.Full,
  606. &address.Street,
  607. &address.City,
  608. &address.Region,
  609. &address.Country,
  610. &address.Zip,
  611. )
  612. return address, err
  613. }
  614. }
  615. */
  616. req, err := http.NewRequest("POST",
  617. "https://rate-gps.nationalmi.com/rates/productRateQuote",
  618. bytes.NewBuffer(body))
  619. req.Header.Add("Content-Type", "application/json")
  620. req.AddCookie(&http.Cookie{
  621. Name: "nmirategps_email",
  622. Value: os.Getenv("NationalMIEmail")})
  623. resp, err := http.DefaultClient.Do(req)
  624. var res map[string]interface{}
  625. var result []MI
  626. if resp.StatusCode != 200 {
  627. log.Printf("the status: %v\nthe resp: %v\n the req: %v\n the body: %v\n",
  628. resp.Status, resp, req.Body, bytes.NewBuffer(body))
  629. } else {
  630. json.NewDecoder(resp.Body).Decode(&res)
  631. // estimate.Loans[pos].Mi = res
  632. // Parse res into result here
  633. }
  634. log.Println(err)
  635. return result
  636. }
  637. // Make comparison PDF
  638. func generatePDF(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  639. }
  640. func login(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  641. var id int
  642. var role string
  643. var err error
  644. var user User
  645. json.NewDecoder(r.Body).Decode(&user)
  646. row := db.QueryRow(
  647. `SELECT id, role FROM user WHERE email = ? AND password = sha2(?, 256)`,
  648. user.Email, user.Password,
  649. )
  650. err = row.Scan(&id, &role)
  651. if err != nil {
  652. http.Error(w, "Invalid Credentials.", http.StatusUnauthorized)
  653. return
  654. }
  655. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  656. UserClaims{ Id: id, Role: role,
  657. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  658. tokenStr, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
  659. if err != nil {
  660. log.Println("Token could not be signed: ", err, tokenStr)
  661. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  662. return
  663. }
  664. cookie := http.Cookie{Name: "skouter",
  665. Value: tokenStr,
  666. Path: "/",
  667. Expires: time.Now().Add(time.Hour * 24)}
  668. http.SetCookie(w, &cookie)
  669. _, err = w.Write([]byte(tokenStr))
  670. if err != nil {
  671. http.Error(w,
  672. "Could not complete token write.",
  673. http.StatusInternalServerError)}
  674. }
  675. func getToken(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  676. claims, err := getClaims(r)
  677. // Will verify existing signature and expiry time
  678. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  679. UserClaims{ Id: claims.Id, Role: claims.Role,
  680. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  681. tokenStr, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
  682. if err != nil {
  683. log.Println("Token could not be signed: ", err, tokenStr)
  684. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  685. return
  686. }
  687. cookie := http.Cookie{Name: "skouter",
  688. Value: tokenStr,
  689. Path: "/",
  690. Expires: time.Now().Add(time.Hour * 24)}
  691. http.SetCookie(w, &cookie)
  692. _, err = w.Write([]byte(tokenStr))
  693. if err != nil {
  694. http.Error(w,
  695. "Could not complete token write.",
  696. http.StatusInternalServerError)}
  697. }
  698. func getClaims(r *http.Request) (UserClaims, error) {
  699. claims := new(UserClaims)
  700. _, tokenStr, found := strings.Cut(r.Header.Get("Authorization"), " ")
  701. if !found {
  702. return *claims, errors.New("Token not found")
  703. }
  704. // Pull token payload into UserClaims
  705. _, err := jwt.ParseWithClaims(tokenStr, claims,
  706. func(token *jwt.Token) (any, error) {
  707. return []byte(os.Getenv("JWT_SECRET")), nil
  708. })
  709. if err != nil {
  710. return *claims, err
  711. }
  712. if err = claims.Valid(); err != nil {
  713. return *claims, err
  714. }
  715. return *claims, nil
  716. }
  717. func guard(r *http.Request, required int) bool {
  718. claims, err := getClaims(r)
  719. if err != nil { return false }
  720. if roles[claims.Role] < required { return false }
  721. return true
  722. }
  723. // Inserts an address and returns it's ID along with any errors.
  724. func insertAddress(db *sql.DB, address Address) (int, error){
  725. var query string
  726. var row *sql.Row
  727. var err error
  728. var id int // Inserted user's id
  729. query = `INSERT INTO address
  730. (
  731. full_address,
  732. street,
  733. city,
  734. region,
  735. country,
  736. zip
  737. )
  738. VALUES (?, ?, ?, ?, ?, ?)
  739. RETURNING id
  740. `
  741. row = db.QueryRow(query,
  742. address.Full,
  743. address.Street,
  744. address.City,
  745. address.Region,
  746. address.Country,
  747. address.Zip,
  748. )
  749. err = row.Scan(&id)
  750. return id, err
  751. }
  752. // Inserts an address and returns it's ID along with any errors.
  753. func insertBranch(db *sql.DB, branch Branch) (int, error){
  754. var query string
  755. var row *sql.Row
  756. var err error
  757. var id int // Inserted user's id
  758. query = `INSERT INTO branch
  759. (
  760. name,
  761. type,
  762. letterhead,
  763. num,
  764. phone,
  765. address
  766. )
  767. VALUES (?, ?, ?, ?, ?, ?)
  768. RETURNING id
  769. `
  770. row = db.QueryRow(query,
  771. branch.Name,
  772. branch.Type,
  773. branch.Letterhead,
  774. branch.Num,
  775. branch.Phone,
  776. branch.Address.Id,
  777. )
  778. err = row.Scan(&id)
  779. return id, err
  780. }
  781. // Inserts an address and returns it's ID along with any errors.
  782. func insertLicense(db *sql.DB, license License) (int, error) {
  783. var query string
  784. var row *sql.Row
  785. var err error
  786. var id int // Inserted license's id
  787. query = `INSERT INTO license
  788. (
  789. user_id,
  790. type,
  791. num
  792. )
  793. VALUES (?, ?, ?)
  794. RETURNING id
  795. `
  796. row = db.QueryRow(query,
  797. license.UserId,
  798. license.Type,
  799. license.Num,
  800. )
  801. err = row.Scan(&id)
  802. return id, err
  803. }
  804. func queryLicense(db *sql.DB, user int) ( License, error ) {
  805. var license License = License{UserId: user}
  806. var err error
  807. row := db.QueryRow(
  808. `SELECT id, type, num FROM license WHERE user_id = ?`,
  809. user)
  810. err = row.Scan(
  811. &license.Id,
  812. &license.Type,
  813. &license.Num,
  814. )
  815. return license, err
  816. }
  817. func queryAddress(db *sql.DB, id int) ( Address, error ) {
  818. var address Address = Address{Id: id}
  819. var err error
  820. row := db.QueryRow(
  821. `SELECT id, full_address, street, city, region, country, zip
  822. FROM address WHERE id = ?`, id)
  823. err = row.Scan(
  824. &address.Id,
  825. &address.Full,
  826. &address.Street,
  827. &address.City,
  828. &address.Region,
  829. &address.Country,
  830. &address.Zip,
  831. )
  832. return address, err
  833. }
  834. func queryBranch(db *sql.DB, id int) ( Branch, error ) {
  835. var branch Branch = Branch{Id: id}
  836. var err error
  837. row := db.QueryRow(
  838. `SELECT id, name, type, letterhead, num, phone, address
  839. FROM branch WHERE id = ?`, id)
  840. err = row.Scan(
  841. &branch.Id,
  842. &branch.Name,
  843. &branch.Type,
  844. &branch.Letterhead,
  845. &branch.Num,
  846. &branch.Phone,
  847. &branch.Address.Id,
  848. )
  849. return branch, err
  850. }
  851. func queryUser(db *sql.DB, id int) (User, error ) {
  852. var user User
  853. var query string
  854. var err error
  855. query = `SELECT
  856. u.id,
  857. u.email,
  858. u.first_name,
  859. u.last_name,
  860. coalesce(u.branch_id, 0),
  861. u.country,
  862. u.title,
  863. coalesce(u.status, ''),
  864. u.verified,
  865. u.role,
  866. u.address,
  867. u.phone
  868. FROM user u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
  869. `
  870. row := db.QueryRow(query, id)
  871. if err != nil {
  872. return user, err
  873. }
  874. err = row.Scan(
  875. &user.Id,
  876. &user.Email,
  877. &user.FirstName,
  878. &user.LastName,
  879. &user.Branch.Id,
  880. &user.Country,
  881. &user.Title,
  882. &user.Status,
  883. &user.Verified,
  884. &user.Role,
  885. &user.Address.Id,
  886. &user.Phone,
  887. )
  888. if err != nil {
  889. return user, err
  890. }
  891. user.Address, err = queryAddress(db, user.Address.Id)
  892. if err != nil {
  893. return user, err
  894. }
  895. user.Branch, err = queryBranch(db, user.Branch.Id)
  896. if err != nil {
  897. return user, err
  898. }
  899. return user, nil
  900. }
  901. // Can probably be deleted.
  902. func queryUsers(db *sql.DB, id int) ( []User, error ) {
  903. var users []User
  904. var query string
  905. var rows *sql.Rows
  906. var err error
  907. query = `SELECT
  908. u.id,
  909. u.email,
  910. u.first_name,
  911. u.last_name,
  912. coalesce(u.branch_id, 0),
  913. u.country,
  914. u.title,
  915. coalesce(u.status, ''),
  916. u.verified,
  917. u.role,
  918. u.address,
  919. u.phone
  920. FROM user u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
  921. `
  922. rows, err = db.Query(query, id)
  923. if err != nil {
  924. return users, err
  925. }
  926. defer rows.Close()
  927. for rows.Next() {
  928. var user User
  929. if err := rows.Scan(
  930. &user.Id,
  931. &user.Email,
  932. &user.FirstName,
  933. &user.LastName,
  934. &user.Branch.Id,
  935. &user.Country,
  936. &user.Title,
  937. &user.Status,
  938. &user.Verified,
  939. &user.Role,
  940. &user.Address.Id,
  941. &user.Phone,
  942. )
  943. err != nil {
  944. return users, err
  945. }
  946. user.Address, err = queryAddress(db, user.Address.Id)
  947. if err != nil {
  948. return users, err
  949. }
  950. user.Branch, err = queryBranch(db, user.Branch.Id)
  951. if err != nil {
  952. return users, err
  953. }
  954. users = append(users, user)
  955. }
  956. // Prevents runtime panics
  957. if len(users) == 0 { return users, errors.New("User not found.") }
  958. return users, nil
  959. }
  960. func (estimate *Estimate) insertResults(db *sql.DB) (error){
  961. var query string
  962. var row *sql.Row
  963. var err error
  964. var id int
  965. query = `INSERT INTO estimate_result
  966. (
  967. loan_id,
  968. loan_payment,
  969. total_monthly,
  970. total_fees,
  971. total_credits,
  972. cash_to_close
  973. )
  974. VALUES (?, ?, ?, ?, ?, ?)
  975. RETURNING id
  976. `
  977. for i := range estimate.Loans {
  978. r := estimate.Loans[i].Result
  979. r.LoanId = estimate.Loans[i].Id
  980. row = db.QueryRow(query,
  981. r.LoanId,
  982. r.LoanPayment,
  983. r.TotalMonthly,
  984. r.TotalFees,
  985. r.TotalCredits,
  986. r.CashToClose,
  987. )
  988. err = row.Scan(&id)
  989. if err != nil { return err }
  990. r.Id = id
  991. }
  992. return nil
  993. }
  994. func insertUser(db *sql.DB, user User) (User, error){
  995. var query string
  996. var row *sql.Row
  997. var err error
  998. var id int // Inserted user's id
  999. user.Address.Id, err = insertAddress(db, user.Address)
  1000. if err != nil { return user, err }
  1001. query = `INSERT INTO user
  1002. (
  1003. email,
  1004. first_name,
  1005. last_name,
  1006. password,
  1007. role,
  1008. title,
  1009. status,
  1010. verified,
  1011. address,
  1012. country,
  1013. branch_id,
  1014. phone,
  1015. created,
  1016. last_login
  1017. )
  1018. VALUES (?, ?, ?, sha2(?, 256), ?, ?, ?, ?, ?, ?,
  1019. CASE @b := ? WHEN 0 THEN NULL ELSE @b END,
  1020. ?, NOW(), NOW())
  1021. RETURNING id
  1022. `
  1023. row = db.QueryRow(query,
  1024. user.Email,
  1025. user.FirstName,
  1026. user.LastName,
  1027. user.Password,
  1028. user.Role,
  1029. user.Title,
  1030. user.Status,
  1031. user.Verified,
  1032. user.Address.Id,
  1033. user.Country,
  1034. user.Branch.Id,
  1035. user.Phone,
  1036. )
  1037. err = row.Scan(&id)
  1038. if err != nil { return User{}, err }
  1039. user, err = queryUser(db, id)
  1040. if err != nil { return User{}, err }
  1041. return user, nil
  1042. }
  1043. func updateAddress(address Address, db *sql.DB) error {
  1044. query := `
  1045. UPDATE address
  1046. SET
  1047. full_address = CASE @e := ? WHEN '' THEN full_address ELSE @e END,
  1048. street = CASE @fn := ? WHEN '' THEN street ELSE @fn END,
  1049. city = CASE @ln := ? WHEN '' THEN city ELSE @ln END,
  1050. region = CASE @r := ? WHEN '' THEN region ELSE @r END,
  1051. country = CASE @r := ? WHEN '' THEN country ELSE @r END,
  1052. zip = CASE @r := ? WHEN '' THEN zip ELSE @r END
  1053. WHERE id = ?
  1054. `
  1055. _, err := db.Exec(query,
  1056. address.Full,
  1057. address.Street,
  1058. address.City,
  1059. address.Region,
  1060. address.Country,
  1061. address.Zip,
  1062. address.Id,
  1063. )
  1064. return err
  1065. }
  1066. func updateUser(user User, db *sql.DB) error {
  1067. query := `
  1068. UPDATE user
  1069. SET
  1070. email = CASE @e := ? WHEN '' THEN email ELSE @e END,
  1071. first_name = CASE @fn := ? WHEN '' THEN first_name ELSE @fn END,
  1072. last_name = CASE @ln := ? WHEN '' THEN last_name ELSE @ln END,
  1073. role = CASE @r := ? WHEN '' THEN role ELSE @r END,
  1074. password = CASE @p := ? WHEN '' THEN password ELSE sha2(@p, 256) END
  1075. WHERE id = ?
  1076. `
  1077. _, err := db.Exec(query,
  1078. user.Email,
  1079. user.FirstName,
  1080. user.LastName,
  1081. user.Role,
  1082. user.Password,
  1083. user.Id,
  1084. )
  1085. return err
  1086. }
  1087. func getUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1088. claims, err := getClaims(r)
  1089. if err != nil { w.WriteHeader(500); return }
  1090. user, err := queryUser(db, claims.Id)
  1091. if err != nil { w.WriteHeader(422); log.Println(err); return }
  1092. json.NewEncoder(w).Encode(user)
  1093. }
  1094. func getUsers(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1095. users, err := queryUsers(db, 0)
  1096. if err != nil {
  1097. w.WriteHeader(http.StatusInternalServerError)
  1098. return
  1099. }
  1100. json.NewEncoder(w).Encode(users)
  1101. }
  1102. // Updates a user using only specified values in the JSON body
  1103. func setUser(user User, db *sql.DB) error {
  1104. _, err := mail.ParseAddress(user.Email)
  1105. if err != nil { return err }
  1106. if roles[user.Role] == 0 {
  1107. return errors.New("Invalid role")
  1108. }
  1109. err = updateUser(user, db)
  1110. if err != nil { return err }
  1111. err = updateAddress(user.Address, db)
  1112. if err != nil { return err }
  1113. return nil
  1114. }
  1115. func patchUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1116. var user User
  1117. err := json.NewDecoder(r.Body).Decode(&user)
  1118. if err != nil { http.Error(w, "Invalid fields", 422); return }
  1119. err = setUser(user, db)
  1120. if err != nil { http.Error(w, err.Error(), 422); return }
  1121. }
  1122. // Update specified fields of the user specified in the claim
  1123. func patchSelf(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1124. claim, err := getClaims(r)
  1125. var user User
  1126. json.NewDecoder(r.Body).Decode(&user)
  1127. // First check that the target user to be updated is the same as the claim id, and
  1128. // their role is unchanged.
  1129. if err != nil || claim.Id != user.Id {
  1130. http.Error(w, "Target user's id does not match claim.", 401)
  1131. return
  1132. }
  1133. if claim.Role != user.Role && user.Role != "" {
  1134. http.Error(w, "Administrator required to escalate role.", 401)
  1135. return
  1136. }
  1137. err = setUser(user, db)
  1138. if err != nil { http.Error(w, err.Error(), 422); return }
  1139. }
  1140. func deleteUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1141. var user User
  1142. err := json.NewDecoder(r.Body).Decode(&user)
  1143. if err != nil {
  1144. http.Error(w, "Invalid fields.", 422)
  1145. return
  1146. }
  1147. query := `DELETE FROM user WHERE id = ?`
  1148. _, err = db.Exec(query, user.Id)
  1149. if err != nil {
  1150. http.Error(w, "Could not delete.", 500)
  1151. }
  1152. }
  1153. func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1154. var user User
  1155. err := json.NewDecoder(r.Body).Decode(&user)
  1156. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  1157. _, err = mail.ParseAddress(user.Email)
  1158. if err != nil { http.Error(w, "Invalid email.", 422); return }
  1159. if roles[user.Role] == 0 {
  1160. http.Error(w, "Invalid role.", 422)
  1161. }
  1162. user, err = insertUser(db, user)
  1163. if err != nil { http.Error(w, "Error creating user.", 422); return }
  1164. json.NewEncoder(w).Encode(user)
  1165. }
  1166. func checkPassword(db *sql.DB, id int, pass string) bool {
  1167. var p string
  1168. query := `SELECT
  1169. password
  1170. FROM user WHERE user.id = ? AND password = sha2(?, 256)
  1171. `
  1172. row := db.QueryRow(query, id, pass)
  1173. err := row.Scan(&p)
  1174. if err != nil { return false }
  1175. return true
  1176. }
  1177. func setPassword(db *sql.DB, id int, pass string) error {
  1178. query := `UPDATE user
  1179. SET password = sha2(?, 256)
  1180. WHERE user.id = ?
  1181. `
  1182. _, err := db.Exec(query, pass, id)
  1183. if err != nil { return errors.New("Could not insert password.") }
  1184. return nil
  1185. }
  1186. func changePassword(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1187. var pass Password
  1188. claim, err := getClaims(r)
  1189. err = json.NewDecoder(r.Body).Decode(&pass)
  1190. if err != nil { http.Error(w, "Bad fields.", 422); return }
  1191. if checkPassword(db, claim.Id, pass.Old) {
  1192. err = setPassword(db, claim.Id, pass.New)
  1193. } else {
  1194. http.Error(w, "Incorrect old password.", 401)
  1195. return
  1196. }
  1197. if err != nil { http.Error(w, err.Error(), 500); return }
  1198. }
  1199. func fetchAvatar(db *sql.DB, user int) ( []byte, error ) {
  1200. var img []byte
  1201. var query string
  1202. var err error
  1203. query = `SELECT
  1204. avatar
  1205. FROM user WHERE user.id = ?
  1206. `
  1207. row := db.QueryRow(query, user)
  1208. err = row.Scan(&img)
  1209. if err != nil {
  1210. return img, err
  1211. }
  1212. return img, nil
  1213. }
  1214. func insertAvatar(db *sql.DB, user int, img []byte) error {
  1215. query := `UPDATE user
  1216. SET avatar = ?
  1217. WHERE id = ?
  1218. `
  1219. _, err := db.Exec(query, img, user)
  1220. if err != nil {
  1221. return err
  1222. }
  1223. return nil
  1224. }
  1225. func fetchLetterhead(db *sql.DB, user int) ( []byte, error ) {
  1226. var img []byte
  1227. var query string
  1228. var err error
  1229. query = `SELECT
  1230. letterhead
  1231. FROM user WHERE user.id = ?
  1232. `
  1233. row := db.QueryRow(query, user)
  1234. err = row.Scan(&img)
  1235. if err != nil {
  1236. return img, err
  1237. }
  1238. return img, nil
  1239. }
  1240. func insertLetterhead(db *sql.DB, user int, img []byte) error {
  1241. query := `UPDATE user
  1242. SET letterhead = ?
  1243. WHERE id = ?
  1244. `
  1245. _, err := db.Exec(query, img, user)
  1246. if err != nil {
  1247. return err
  1248. }
  1249. return nil
  1250. }
  1251. func setAvatar(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1252. var validTypes []string = []string{"image/png", "image/jpeg"}
  1253. var isValidType bool
  1254. claims, err := getClaims(r)
  1255. if err != nil { http.Error(w, "Invalid token.", 422); return }
  1256. img, err := io.ReadAll(r.Body)
  1257. if err != nil { http.Error(w, "Invalid file.", 422); return }
  1258. for _, v := range validTypes {
  1259. if v == http.DetectContentType(img) { isValidType = true }
  1260. }
  1261. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  1262. err = insertAvatar(db, claims.Id, img)
  1263. if err != nil { http.Error(w, "Could not insert.", 500); return }
  1264. }
  1265. func getAvatar(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1266. claims, err := getClaims(r)
  1267. if err != nil { http.Error(w, "Invalid token.", 422); return }
  1268. img, err := fetchAvatar(db, claims.Id)
  1269. if err != nil { http.Error(w, "Could not retrieve.", 500); return }
  1270. w.Header().Set("Content-Type", http.DetectContentType(img))
  1271. w.Write(img)
  1272. }
  1273. func setLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1274. var validTypes []string = []string{"image/png", "image/jpeg"}
  1275. var isValidType bool
  1276. claims, err := getClaims(r)
  1277. if err != nil { http.Error(w, "Invalid token.", 422); return }
  1278. img, err := io.ReadAll(r.Body)
  1279. if err != nil { http.Error(w, "Invalid file.", 422); return }
  1280. for _, v := range validTypes {
  1281. if v == http.DetectContentType(img) { isValidType = true }
  1282. }
  1283. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  1284. err = insertLetterhead(db, claims.Id, img)
  1285. if err != nil { http.Error(w, "Could not insert.", 500); return }
  1286. }
  1287. func getLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1288. claims, err := getClaims(r)
  1289. if err != nil { http.Error(w, "Invalid token.", 422); return }
  1290. img, err := fetchLetterhead(db, claims.Id)
  1291. if err != nil { http.Error(w, "Could not retrieve.", 500); return }
  1292. w.Header().Set("Content-Type", http.DetectContentType(img))
  1293. w.Write(img)
  1294. }
  1295. func queryBorrower(db *sql.DB, id int) ( Borrower, error ) {
  1296. var borrower Borrower
  1297. var query string
  1298. var err error
  1299. query = `SELECT
  1300. l.id,
  1301. l.credit_score,
  1302. l.num,
  1303. l.monthly_income
  1304. FROM borrower l WHERE l.id = ?
  1305. `
  1306. row := db.QueryRow(query, id)
  1307. err = row.Scan(
  1308. borrower.Id,
  1309. borrower.Credit,
  1310. borrower.Num,
  1311. borrower.Income,
  1312. )
  1313. if err != nil {
  1314. return borrower, err
  1315. }
  1316. return borrower, nil
  1317. }
  1318. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  1319. func getResults(db *sql.DB, e int, id int) ( []Result, error ) {
  1320. var results []Result
  1321. var query string
  1322. var rows *sql.Rows
  1323. var err error
  1324. query = `SELECT
  1325. r.id,
  1326. loan_id,
  1327. loan_payment,
  1328. total_monthly,
  1329. total_fees,
  1330. total_credits,
  1331. cash_to_close
  1332. FROM estimate_result r
  1333. INNER JOIN loan
  1334. ON r.loan_id = loan.id
  1335. WHERE r.id = CASE @e := ? WHEN 0 THEN r.id ELSE @e END
  1336. AND loan.estimate_id = ?
  1337. `
  1338. rows, err = db.Query(query, id, e)
  1339. if err != nil {
  1340. return results, err
  1341. }
  1342. defer rows.Close()
  1343. for rows.Next() {
  1344. var result Result
  1345. if err := rows.Scan(
  1346. &result.Id,
  1347. &result.LoanId,
  1348. &result.LoanPayment,
  1349. &result.TotalMonthly,
  1350. &result.TotalFees,
  1351. &result.TotalCredits,
  1352. &result.CashToClose,
  1353. )
  1354. err != nil {
  1355. return results, err
  1356. }
  1357. results = append(results, result)
  1358. }
  1359. // Prevents runtime panics
  1360. // if len(results) == 0 { return results, errors.New("Result not found.") }
  1361. return results, nil
  1362. }
  1363. // Retrieve an estimate result with a specified loan id
  1364. func getResult(db *sql.DB, loan int) ( Result, error ) {
  1365. var result Result
  1366. var query string
  1367. var err error
  1368. query = `SELECT
  1369. r.id,
  1370. loan_id,
  1371. loan_payment,
  1372. total_monthly,
  1373. total_fees,
  1374. total_credits,
  1375. cash_to_close
  1376. FROM estimate_result r
  1377. INNER JOIN loan
  1378. ON r.loan_id = loan.id
  1379. WHERE loan.Id = ?
  1380. `
  1381. row := db.QueryRow(query, loan)
  1382. err = row.Scan(
  1383. &result.Id,
  1384. &result.LoanId,
  1385. &result.LoanPayment,
  1386. &result.TotalMonthly,
  1387. &result.TotalFees,
  1388. &result.TotalCredits,
  1389. &result.CashToClose,
  1390. )
  1391. if err != nil {
  1392. return result, err
  1393. }
  1394. return result, nil
  1395. }
  1396. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  1397. func getLoans(db *sql.DB, e int, id int) ( []Loan, error ) {
  1398. var loans []Loan
  1399. var query string
  1400. var rows *sql.Rows
  1401. var err error
  1402. query = `SELECT
  1403. l.id,
  1404. l.type_id,
  1405. l.estimate_id,
  1406. l.amount,
  1407. l.term,
  1408. l.interest,
  1409. l.ltv,
  1410. l.dti,
  1411. l.hoi,
  1412. l.tax,
  1413. l.name
  1414. FROM loan l WHERE l.id = CASE @e := ? WHEN 0 THEN l.id ELSE @e END AND
  1415. l.estimate_id = ?
  1416. `
  1417. rows, err = db.Query(query, id, e)
  1418. if err != nil {
  1419. return loans, err
  1420. }
  1421. defer rows.Close()
  1422. for rows.Next() {
  1423. var loan Loan
  1424. if err := rows.Scan(
  1425. &loan.Id,
  1426. &loan.Type.Id,
  1427. &loan.EstimateId,
  1428. &loan.Amount,
  1429. &loan.Term,
  1430. &loan.Interest,
  1431. &loan.Ltv,
  1432. &loan.Dti,
  1433. &loan.Hoi,
  1434. &loan.Tax,
  1435. &loan.Name,
  1436. )
  1437. err != nil {
  1438. return loans, err
  1439. }
  1440. mi, err := getMi(db, loan.Id)
  1441. if err != nil {
  1442. return loans, err
  1443. }
  1444. loan.Mi = mi
  1445. fees, err := getFees(db, loan.Id)
  1446. if err != nil {
  1447. return loans, err
  1448. }
  1449. loan.Fees = fees
  1450. loan.Result, err = getResult(db, loan.Id)
  1451. if err != nil {
  1452. return loans, err
  1453. }
  1454. loan.Type, err = getLoanType(db, loan.Type.Id)
  1455. if err != nil {
  1456. return loans, err
  1457. }
  1458. loans = append(loans, loan)
  1459. }
  1460. // Prevents runtime panics
  1461. if len(loans) == 0 { return loans, errors.New("Loan not found.") }
  1462. return loans, nil
  1463. }
  1464. func getEstimates(db *sql.DB, id int, user int) ( []Estimate, error ) {
  1465. var estimates []Estimate
  1466. var query string
  1467. var rows *sql.Rows
  1468. var err error
  1469. query = `SELECT
  1470. id,
  1471. user_id,
  1472. borrower_id,
  1473. transaction,
  1474. price,
  1475. property,
  1476. occupancy,
  1477. zip,
  1478. pud
  1479. FROM estimate WHERE id = CASE @e := ? WHEN 0 THEN id ELSE @e END AND
  1480. user_id = CASE @e := ? WHEN 0 THEN user_id ELSE @e END
  1481. `
  1482. rows, err = db.Query(query, id, user)
  1483. if err != nil {
  1484. return estimates, err
  1485. }
  1486. defer rows.Close()
  1487. for rows.Next() {
  1488. var estimate Estimate
  1489. if err := rows.Scan(
  1490. &estimate.Id,
  1491. &estimate.User,
  1492. &estimate.Borrower.Id,
  1493. &estimate.Transaction,
  1494. &estimate.Price,
  1495. &estimate.Property,
  1496. &estimate.Occupancy,
  1497. &estimate.Zip,
  1498. &estimate.Pud,
  1499. )
  1500. err != nil {
  1501. return estimates, err
  1502. }
  1503. borrower, err := getBorrower(db, estimate.Borrower.Id)
  1504. if err != nil {
  1505. return estimates, err
  1506. }
  1507. estimate.Borrower = borrower
  1508. estimates = append(estimates, estimate)
  1509. }
  1510. // Prevents runtime panics
  1511. if len(estimates) == 0 { return estimates, errors.New("Estimate not found.") }
  1512. for i := range estimates {
  1513. estimates[i].Loans, err = getLoans(db, estimates[i].Id, 0)
  1514. if err != nil { return estimates, err }
  1515. }
  1516. return estimates, nil
  1517. }
  1518. // Accepts a borrower struct and returns the id of the inserted borrower and
  1519. // any related error.
  1520. func insertBorrower(db *sql.DB, borrower Borrower) (int, error) {
  1521. var query string
  1522. var row *sql.Row
  1523. var err error
  1524. var id int // Inserted loan's id
  1525. query = `INSERT INTO borrower
  1526. (
  1527. credit_score,
  1528. monthly_income,
  1529. num
  1530. )
  1531. VALUES (?, ?, ?)
  1532. RETURNING id
  1533. `
  1534. row = db.QueryRow(query,
  1535. borrower.Credit,
  1536. borrower.Income,
  1537. borrower.Num,
  1538. )
  1539. err = row.Scan(&id)
  1540. if err != nil { return 0, err }
  1541. return id, nil
  1542. }
  1543. func insertMi(db *sql.DB, mi MI) (int, error) {
  1544. var id int
  1545. query := `INSERT INTO mi
  1546. (
  1547. type,
  1548. label,
  1549. lender,
  1550. rate,
  1551. premium,
  1552. upfront,
  1553. five_year_total,
  1554. initial_premium,
  1555. initial_rate,
  1556. initial_amount
  1557. )
  1558. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1559. RETURNING id`
  1560. row := db.QueryRow(query,
  1561. &mi.Type,
  1562. &mi.Label,
  1563. &mi.Lender,
  1564. &mi.Rate,
  1565. &mi.Premium,
  1566. &mi.Upfront,
  1567. &mi.FiveYearTotal,
  1568. &mi.InitialAllInPremium,
  1569. &mi.InitialAllInRate,
  1570. &mi.InitialAmount,
  1571. )
  1572. err := row.Scan(&id)
  1573. if err != nil { return 0, err }
  1574. return id, nil
  1575. }
  1576. func insertFee(db *sql.DB, fee Fee) (int, error) {
  1577. var id int
  1578. query := `INSERT INTO fee
  1579. (loan_id, amount, perc, type, notes, name, category)
  1580. VALUES (?, ?, ?, ?, ?, ?, ?)
  1581. RETURNING id`
  1582. row := db.QueryRow(query,
  1583. fee.LoanId,
  1584. fee.Amount,
  1585. fee.Perc,
  1586. fee.Type,
  1587. fee.Notes,
  1588. fee.Name,
  1589. fee.Category,
  1590. )
  1591. err := row.Scan(&id)
  1592. if err != nil { return 0, err }
  1593. return id, nil
  1594. }
  1595. func insertLoanType(db *sql.DB, lt LoanType) (int, error) {
  1596. var id int
  1597. query := `INSERT INTO loan_type (branch_id, user_id, name)
  1598. VALUES (NULLIF(?, 0), NULLIF(?, 0), ?)
  1599. RETURNING id`
  1600. row := db.QueryRow(query,
  1601. lt.Branch,
  1602. lt.User,
  1603. lt.Name,
  1604. )
  1605. err := row.Scan(&id)
  1606. if err != nil { return 0, err }
  1607. return id, nil
  1608. }
  1609. func (loan *Loan) insertLoan(db *sql.DB) error {
  1610. var query string
  1611. var row *sql.Row
  1612. var err error
  1613. query = `INSERT INTO loan
  1614. (
  1615. estimate_id,
  1616. type_id,
  1617. amount,
  1618. term,
  1619. interest,
  1620. ltv,
  1621. dti,
  1622. hoi,
  1623. tax,
  1624. name
  1625. )
  1626. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1627. RETURNING id
  1628. `
  1629. row = db.QueryRow(query,
  1630. loan.EstimateId,
  1631. loan.Type.Id,
  1632. loan.Amount,
  1633. loan.Term,
  1634. loan.Interest,
  1635. loan.Ltv,
  1636. loan.Dti,
  1637. loan.Hoi,
  1638. loan.Tax,
  1639. loan.Name,
  1640. )
  1641. err = row.Scan(&loan.Id)
  1642. if err != nil { return err }
  1643. _, err = insertMi(db, loan.Mi)
  1644. if err != nil { return err }
  1645. for i := range loan.Fees {
  1646. loan.Fees[i].LoanId = loan.Id
  1647. _, err := insertFee(db, loan.Fees[i])
  1648. if err != nil { return err }
  1649. }
  1650. return nil
  1651. }
  1652. func (estimate *Estimate) insertEstimate(db *sql.DB) (error){
  1653. var query string
  1654. var row *sql.Row
  1655. var err error
  1656. // var id int // Inserted estimate's id
  1657. estimate.Borrower.Id, err = insertBorrower(db, estimate.Borrower)
  1658. if err != nil { return err }
  1659. query = `INSERT INTO estimate
  1660. (
  1661. user_id,
  1662. borrower_id,
  1663. transaction,
  1664. price,
  1665. property,
  1666. occupancy,
  1667. zip,
  1668. pud
  1669. )
  1670. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  1671. RETURNING id
  1672. `
  1673. row = db.QueryRow(query,
  1674. estimate.User,
  1675. estimate.Borrower.Id,
  1676. estimate.Transaction,
  1677. estimate.Price,
  1678. estimate.Property,
  1679. estimate.Occupancy,
  1680. estimate.Zip,
  1681. estimate.Pud,
  1682. )
  1683. err = row.Scan(&estimate.Id)
  1684. if err != nil { return err }
  1685. for i := range estimate.Loans {
  1686. estimate.Loans[i].EstimateId = estimate.Id
  1687. err = estimate.Loans[i].insertLoan(db)
  1688. if err != nil { return err }
  1689. }
  1690. return nil
  1691. }
  1692. func createEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1693. var estimate Estimate
  1694. err := json.NewDecoder(r.Body).Decode(&estimate)
  1695. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  1696. claims, err := getClaims(r)
  1697. estimate.User = claims.Id
  1698. err = estimate.insertEstimate(db)
  1699. if err != nil { http.Error(w, err.Error(), 422); return }
  1700. estimate.makeResults()
  1701. err = estimate.insertResults(db)
  1702. if err != nil { http.Error(w, err.Error(), 500); return }
  1703. e, err := getEstimates(db, estimate.Id, 0)
  1704. if err != nil { http.Error(w, err.Error(), 500); return }
  1705. json.NewEncoder(w).Encode(e[0])
  1706. }
  1707. // Query all estimates that belong to the current user
  1708. func fetchEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1709. var estimates []Estimate
  1710. claims, err := getClaims(r)
  1711. estimates, err = getEstimates(db, 0, claims.Id)
  1712. if err != nil { http.Error(w, err.Error(), 500); return }
  1713. json.NewEncoder(w).Encode(estimates)
  1714. }
  1715. func checkConventional(l Loan, b Borrower) error {
  1716. if b.Credit < 620 {
  1717. return errors.New("Credit score too low for conventional loan")
  1718. }
  1719. // Buyer needs to put down 20% to avoid mortgage insurance
  1720. if (l.Ltv > 80 && l.Mi.Rate == 0) {
  1721. return errors.New(fmt.Sprintln(
  1722. l.Name,
  1723. "down payment must be 20% to avoid insurance",
  1724. ))
  1725. }
  1726. return nil
  1727. }
  1728. func checkFHA(l Loan, b Borrower) error {
  1729. if b.Credit < 500 {
  1730. return errors.New("Credit score too low for FHA loan")
  1731. }
  1732. if (l.Ltv > 96.5) {
  1733. return errors.New("FHA down payment must be at least 3.5%")
  1734. }
  1735. if (b.Credit < 580 && l.Ltv > 90) {
  1736. return errors.New("FHA down payment must be at least 10%")
  1737. }
  1738. // Debt-to-income must be below 45% if credit score is below 580.
  1739. if (b.Credit < 580 && l.Dti > 45) {
  1740. return errors.New(fmt.Sprintln(
  1741. l.Name, "debt to income is too high for credit score.",
  1742. ))
  1743. }
  1744. return nil
  1745. }
  1746. // Loan option for veterans with no set rules. Mainly placeholder.
  1747. func checkVA(l Loan, b Borrower) error {
  1748. return nil
  1749. }
  1750. // Loan option for residents of rural areas with no set rules.
  1751. // Mainly placeholder.
  1752. func checkUSDA(l Loan, b Borrower) error {
  1753. return nil
  1754. }
  1755. // Should also check loan amount limit maybe with an API.
  1756. func checkEstimate(e Estimate) error {
  1757. if e.Property == "" { return errors.New("Empty property type") }
  1758. if e.Price == 0 { return errors.New("Empty property price") }
  1759. if e.Borrower.Num == 0 {
  1760. return errors.New("Missing number of borrowers")
  1761. }
  1762. if e.Borrower.Credit == 0 {
  1763. return errors.New("Missing borrower credit score")
  1764. }
  1765. if e.Borrower.Income == 0 {
  1766. return errors.New("Missing borrower credit income")
  1767. }
  1768. for _, l := range e.Loans {
  1769. if l.Amount == 0 {
  1770. return errors.New(fmt.Sprintln(l.Name, "loan amount cannot be zero"))
  1771. }
  1772. if l.Term == 0 {
  1773. return errors.New(fmt.Sprintln(l.Name, "loan term cannot be zero"))
  1774. }
  1775. if l.Interest == 0 {
  1776. return errors.New(fmt.Sprintln(l.Name, "loan interest cannot be zero"))
  1777. }
  1778. // Can be used to check rules for specific loan types
  1779. var err error
  1780. switch l.Type.Id {
  1781. case 1:
  1782. err = checkConventional(l, e.Borrower)
  1783. case 2:
  1784. err = checkFHA(l, e.Borrower)
  1785. case 3:
  1786. err = checkVA(l, e.Borrower)
  1787. case 4:
  1788. err = checkUSDA(l, e.Borrower)
  1789. default:
  1790. err = errors.New("Invalid loan type")
  1791. }
  1792. if err != nil { return err }
  1793. }
  1794. return nil
  1795. }
  1796. func validateEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1797. var estimate Estimate
  1798. err := json.NewDecoder(r.Body).Decode(&estimate)
  1799. if err != nil { http.Error(w, err.Error(), 422); return }
  1800. err = checkEstimate(estimate)
  1801. if err != nil { http.Error(w, err.Error(), 406); return }
  1802. }
  1803. func checkPdf(w http.ResponseWriter, r *http.Request) {
  1804. db, err := sql.Open("mysql",
  1805. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter_dev",
  1806. os.Getenv("DBUser"),
  1807. os.Getenv("DBPass")))
  1808. // w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  1809. err = db.Ping()
  1810. if err != nil {
  1811. fmt.Println("Bad database configuration: %v\n", err)
  1812. panic(err)
  1813. // maybe os.Exit(1) instead
  1814. }
  1815. estimates, err := getEstimates(db, 1, 0)
  1816. if err != nil { w.WriteHeader(500); return }
  1817. // claims, err := getClaims(r)
  1818. if err != nil { w.WriteHeader(500); return }
  1819. user, err := queryUser(db, 1)
  1820. info := Report{
  1821. Title: "test PDF",
  1822. Name: "idk-random-name",
  1823. User: user,
  1824. Estimate: estimates[0],
  1825. }
  1826. avatar, err := fetchAvatar(db, info.User.Id)
  1827. letterhead, err := fetchLetterhead(db, info.User.Id)
  1828. info.Avatar =
  1829. base64.StdEncoding.EncodeToString(avatar)
  1830. info.Letterhead =
  1831. base64.StdEncoding.EncodeToString(letterhead)
  1832. for l := range info.Estimate.Loans {
  1833. loan := info.Estimate.Loans[l]
  1834. for f := range info.Estimate.Loans[l].Fees {
  1835. if info.Estimate.Loans[l].Fees[f].Amount < 0 {
  1836. loan.Credits = append(loan.Credits, loan.Fees[f])
  1837. }
  1838. }
  1839. }
  1840. err = pages["report"].tpl.ExecuteTemplate(w, "master.tpl", info)
  1841. if err != nil {fmt.Println(err)}
  1842. }
  1843. func getPdf(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1844. var estimate Estimate
  1845. err := json.NewDecoder(r.Body).Decode(&estimate)
  1846. cmd := exec.Command("wkhtmltopdf", "-", "-")
  1847. stdout, err := cmd.StdoutPipe()
  1848. if err != nil {
  1849. w.WriteHeader(500);
  1850. log.Println(err)
  1851. return
  1852. }
  1853. stdin, err := cmd.StdinPipe()
  1854. if err != nil {
  1855. w.WriteHeader(500);
  1856. log.Println(err)
  1857. return
  1858. }
  1859. if err := cmd.Start(); err != nil {
  1860. log.Fatal(err)
  1861. }
  1862. claims, err := getClaims(r)
  1863. if err != nil {
  1864. w.WriteHeader(500);
  1865. log.Println(err)
  1866. return
  1867. }
  1868. user, err := queryUser(db, claims.Id)
  1869. info := Report{
  1870. Title: "test PDF",
  1871. Name: "idk-random-name",
  1872. User: user,
  1873. Estimate: estimate,
  1874. }
  1875. avatar, err := fetchAvatar(db, info.User.Id)
  1876. letterhead, err := fetchLetterhead(db, info.User.Id)
  1877. info.Avatar =
  1878. base64.StdEncoding.EncodeToString(avatar)
  1879. info.Letterhead =
  1880. base64.StdEncoding.EncodeToString(letterhead)
  1881. err = pages["report"].tpl.ExecuteTemplate(stdin, "master.tpl", info)
  1882. if err != nil {
  1883. w.WriteHeader(500);
  1884. log.Println(err)
  1885. return
  1886. }
  1887. stdin.Close()
  1888. buf, err := io.ReadAll(stdout)
  1889. if _, err := w.Write(buf); err != nil {
  1890. w.WriteHeader(500);
  1891. log.Println(err)
  1892. return
  1893. }
  1894. if err := cmd.Wait(); err != nil {
  1895. log.Println(err)
  1896. return
  1897. }
  1898. }
  1899. func clipLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1900. var validTypes []string = []string{"image/png", "image/jpeg"}
  1901. var isValidType bool
  1902. var err error
  1903. // claims, err := getClaims(r)
  1904. if err != nil { http.Error(w, "Invalid token.", 422); return }
  1905. img, t, err := image.Decode(r.Body)
  1906. if err != nil {
  1907. http.Error(w, "Invalid file, JPEG and PNG only.", 422)
  1908. return
  1909. }
  1910. for _, v := range validTypes {
  1911. if v == "image/"+t { isValidType = true }
  1912. }
  1913. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  1914. g := gift.New(
  1915. gift.ResizeToFit(400, 200, gift.LanczosResampling),
  1916. )
  1917. dst := image.NewRGBA(g.Bounds(img.Bounds()))
  1918. g.Draw(dst, img)
  1919. w.Header().Set("Content-Type", "image/png")
  1920. err = png.Encode(w, dst)
  1921. if err != nil { http.Error(w, "Error encoding.", 500); return }
  1922. }
  1923. func api(w http.ResponseWriter, r *http.Request) {
  1924. var args []string
  1925. p := r.URL.Path
  1926. db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
  1927. os.Getenv("DBUser"),
  1928. os.Getenv("DBPass"),
  1929. os.Getenv("DBName"),
  1930. ))
  1931. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  1932. err = db.Ping()
  1933. if err != nil {
  1934. fmt.Println("Bad database configuration: %v\n", err)
  1935. panic(err)
  1936. // maybe os.Exit(1) instead
  1937. }
  1938. switch {
  1939. case match(p, "/api/login", &args) &&
  1940. r.Method == http.MethodPost:
  1941. login(w, db, r)
  1942. case match(p, "/api/token", &args) &&
  1943. r.Method == http.MethodGet && guard(r, 1):
  1944. getToken(w, db, r)
  1945. case match(p, "/api/letterhead", &args) &&
  1946. r.Method == http.MethodPost && guard(r, 1):
  1947. clipLetterhead(w, db, r)
  1948. case match(p, "/api/users", &args) && // Array of all users
  1949. r.Method == http.MethodGet && guard(r, 3):
  1950. getUsers(w, db, r)
  1951. case match(p, "/api/user", &args) &&
  1952. r.Method == http.MethodGet && guard(r, 1):
  1953. getUser(w, db, r)
  1954. case match(p, "/api/user", &args) &&
  1955. r.Method == http.MethodPost &&
  1956. guard(r, 3):
  1957. createUser(w, db, r)
  1958. case match(p, "/api/user", &args) &&
  1959. r.Method == http.MethodPatch &&
  1960. guard(r, 3): // For admin to modify any user
  1961. patchUser(w, db, r)
  1962. case match(p, "/api/user", &args) &&
  1963. r.Method == http.MethodPatch &&
  1964. guard(r, 1): // For employees to modify own accounts
  1965. patchSelf(w, db, r)
  1966. case match(p, "/api/user", &args) &&
  1967. r.Method == http.MethodDelete &&
  1968. guard(r, 3):
  1969. deleteUser(w, db, r)
  1970. case match(p, "/api/user/avatar", &args) &&
  1971. r.Method == http.MethodGet &&
  1972. guard(r, 1):
  1973. getAvatar(w, db, r)
  1974. case match(p, "/api/user/avatar", &args) &&
  1975. r.Method == http.MethodPost &&
  1976. guard(r, 1):
  1977. setAvatar(w, db, r)
  1978. case match(p, "/api/user/letterhead", &args) &&
  1979. r.Method == http.MethodGet &&
  1980. guard(r, 1):
  1981. getLetterhead(w, db, r)
  1982. case match(p, "/api/user/letterhead", &args) &&
  1983. r.Method == http.MethodPost &&
  1984. guard(r, 1):
  1985. setLetterhead(w, db, r)
  1986. case match(p, "/api/user/password", &args) &&
  1987. r.Method == http.MethodPost &&
  1988. guard(r, 1):
  1989. changePassword(w, db, r)
  1990. case match(p, "/api/fees", &args) &&
  1991. r.Method == http.MethodGet &&
  1992. guard(r, 1):
  1993. getFeesTemp(w, db, r)
  1994. case match(p, "/api/fee", &args) &&
  1995. r.Method == http.MethodPost &&
  1996. guard(r, 1):
  1997. createFeesTemp(w, db, r)
  1998. case match(p, "/api/fee", &args) &&
  1999. r.Method == http.MethodDelete &&
  2000. guard(r, 1):
  2001. deleteFeeTemp(w, db, r)
  2002. case match(p, "/api/estimates", &args) &&
  2003. r.Method == http.MethodGet &&
  2004. guard(r, 1):
  2005. fetchEstimate(w, db, r)
  2006. case match(p, "/api/estimate", &args) &&
  2007. r.Method == http.MethodPost &&
  2008. guard(r, 1):
  2009. createEstimate(w, db, r)
  2010. case match(p, "/api/estimate/validate", &args) &&
  2011. r.Method == http.MethodPost &&
  2012. guard(r, 1):
  2013. validateEstimate(w, db, r)
  2014. case match(p, "/api/estimate/summarize", &args) &&
  2015. r.Method == http.MethodPost &&
  2016. guard(r, 1):
  2017. summarize(w, db, r)
  2018. case match(p, "/api/pdf", &args) &&
  2019. r.Method == http.MethodPost &&
  2020. guard(r, 1):
  2021. getPdf(w, db, r)
  2022. default:
  2023. http.Error(w, "Invalid route or token", 404)
  2024. }
  2025. db.Close()
  2026. }
  2027. func route(w http.ResponseWriter, r *http.Request) {
  2028. var page Page
  2029. var args []string
  2030. p := r.URL.Path
  2031. switch {
  2032. case r.Method == "GET" && match(p, "/", &args):
  2033. page = pages[ "home" ]
  2034. case match(p, "/terms", &args):
  2035. page = pages[ "terms" ]
  2036. case match(p, "/app", &args):
  2037. page = pages[ "app" ]
  2038. default:
  2039. http.NotFound(w, r)
  2040. return
  2041. }
  2042. page.Render(w)
  2043. }
  2044. func serve() {
  2045. files := http.FileServer(http.Dir(""))
  2046. http.Handle("/assets/", files)
  2047. http.HandleFunc("/api/", api)
  2048. http.HandleFunc("/", route)
  2049. log.Fatal(http.ListenAndServe(address, nil))
  2050. }
  2051. func dbReset(db *sql.DB) {
  2052. b, err := os.ReadFile("migrations/reset.sql")
  2053. if err != nil {
  2054. log.Fatal(err)
  2055. }
  2056. _, err = db.Exec(string(b))
  2057. if err != nil {
  2058. log.Fatal(err)
  2059. }
  2060. b, err = os.ReadFile("migrations/0_29092022_setup_tables.sql")
  2061. if err != nil {
  2062. log.Fatal(err)
  2063. }
  2064. _, err = db.Exec(string(b))
  2065. if err != nil {
  2066. log.Fatal(err)
  2067. }
  2068. }
  2069. func generateFees(loan Loan) []Fee {
  2070. var fees []Fee
  2071. var fee Fee
  2072. p := gofakeit.Float32Range(0.5, 10)
  2073. size := gofakeit.Number(1, 10)
  2074. for f := 0; f < size; f++ {
  2075. fee = Fee{
  2076. Amount: int(float32(loan.Amount)*p/100),
  2077. Perc: p,
  2078. Name: gofakeit.BuzzWord(),
  2079. Type: feeTypes[gofakeit.Number(0, len(feeTypes) - 1)],
  2080. }
  2081. fees = append(fees, fee)
  2082. }
  2083. return fees
  2084. }
  2085. func generateCredits(loan Loan) []Fee {
  2086. var fees []Fee
  2087. var fee Fee
  2088. p := gofakeit.Float32Range(-10, -0.5)
  2089. size := gofakeit.Number(1, 10)
  2090. for f := 0; f < size; f++ {
  2091. fee = Fee{
  2092. Amount: int(float32(loan.Amount)*p/100),
  2093. Perc: p,
  2094. Name: gofakeit.BuzzWord(),
  2095. Type: feeTypes[gofakeit.Number(0, len(feeTypes) - 1)],
  2096. }
  2097. fees = append(fees, fee)
  2098. }
  2099. return fees
  2100. }
  2101. func seedAddresses(db *sql.DB) []Address {
  2102. addresses := make([]Address, 10)
  2103. for i, a := range addresses {
  2104. a.Street = gofakeit.Street()
  2105. a.City = gofakeit.City()
  2106. a.Region = gofakeit.State()
  2107. a.Country = "Canada"
  2108. a.Full = fmt.Sprintf("%s, %s %s", a.Street, a.City, a.Region)
  2109. id, err := insertAddress(db, a)
  2110. if err != nil {log.Println(err); break}
  2111. addresses[i].Id = id
  2112. }
  2113. return addresses
  2114. }
  2115. func seedBranches(db *sql.DB, addresses []Address) []Branch {
  2116. branches := make([]Branch, 4)
  2117. for i := range branches {
  2118. branches[i].Name = gofakeit.Company()
  2119. branches[i].Type = "NMLS"
  2120. branches[i].Letterhead = gofakeit.ImagePng(400, 200)
  2121. branches[i].Num = gofakeit.HexUint8()
  2122. branches[i].Phone = gofakeit.Phone()
  2123. branches[i].Address.Id = gofakeit.Number(1, 5)
  2124. id, err := insertBranch(db, branches[i])
  2125. if err != nil {log.Println(err); break}
  2126. branches[i].Id = id
  2127. }
  2128. return branches
  2129. }
  2130. func seedUsers(db *sql.DB, addresses []Address, branches []Branch) []User {
  2131. users := make([]User, 10)
  2132. for i := range users {
  2133. p := gofakeit.Person()
  2134. users[i].FirstName = p.FirstName
  2135. users[i].LastName = p.LastName
  2136. users[i].Email = p.Contact.Email
  2137. users[i].Phone = p.Contact.Phone
  2138. users[i].Branch = branches[gofakeit.Number(0, 3)]
  2139. users[i].Address = addresses[gofakeit.Number(1, 9)]
  2140. // users[i].Letterhead = gofakeit.ImagePng(400, 200)
  2141. // users[i].Avatar = gofakeit.ImagePng(200, 200)
  2142. users[i].Country = []string{"Canada", "USA"}[gofakeit.Number(0, 1)]
  2143. users[i].Password = "test123"
  2144. users[i].Verified = true
  2145. users[i].Title = "Loan Officer"
  2146. users[i].Status = "Subscribed"
  2147. users[i].Role = "User"
  2148. }
  2149. users[0].Email = "test@example.com"
  2150. users[0].Email = "test@example.com"
  2151. users[1].Email = "test2@example.com"
  2152. users[1].Status = "Branch"
  2153. users[1].Role = "Manager"
  2154. users[2].Email = "test3@example.com"
  2155. users[2].Status = "Free"
  2156. users[2].Role = "Admin"
  2157. for i := range users {
  2158. u, err := insertUser(db, users[i])
  2159. if err != nil {log.Println(err); break}
  2160. users[i].Id = u.Id
  2161. }
  2162. return users
  2163. }
  2164. func seedLicenses(db *sql.DB, users []User) []License {
  2165. licenses := make([]License, len(users))
  2166. for i := range licenses {
  2167. licenses[i].UserId = users[i].Id
  2168. licenses[i].Type = []string{"NMLS", "FSRA"}[gofakeit.Number(0, 1)]
  2169. licenses[i].Num = gofakeit.UUID()
  2170. id, err := insertLicense(db, licenses[i])
  2171. if err != nil {log.Println(err); break}
  2172. licenses[i].Id = id
  2173. }
  2174. return licenses
  2175. }
  2176. func seedLoanTypes(db *sql.DB) []LoanType {
  2177. var loantypes []LoanType
  2178. var loantype LoanType
  2179. var err error
  2180. loantype = LoanType{Branch: 0, User: 0, Name: "Conventional"}
  2181. loantype.Id, err = insertLoanType(db, loantype)
  2182. if err != nil { panic(err) }
  2183. loantypes = append(loantypes, loantype)
  2184. loantype = LoanType{Branch: 0, User: 0, Name: "FHA"}
  2185. loantype.Id, err = insertLoanType(db, loantype)
  2186. if err != nil { panic(err) }
  2187. loantypes = append(loantypes, loantype)
  2188. loantype = LoanType{Branch: 0, User: 0, Name: "USDA"}
  2189. loantype.Id, err = insertLoanType(db, loantype)
  2190. if err != nil { panic(err) }
  2191. loantypes = append(loantypes, loantype)
  2192. loantype = LoanType{Branch: 0, User: 0, Name: "VA"}
  2193. loantype.Id, err = insertLoanType(db, loantype)
  2194. if err != nil { panic(err) }
  2195. loantypes = append(loantypes, loantype)
  2196. return loantypes
  2197. }
  2198. func seedEstimates(db *sql.DB, users []User, ltypes []LoanType) []Estimate {
  2199. var estimates []Estimate
  2200. var estimate Estimate
  2201. var l Loan
  2202. var err error
  2203. for i := 0; i < 15; i++ {
  2204. estimate = Estimate{}
  2205. estimate.User = users[gofakeit.Number(0, len(users) - 1)].Id
  2206. estimate.Borrower = Borrower{
  2207. Credit: gofakeit.Number(600, 800),
  2208. Income: gofakeit.Number(1000000, 15000000),
  2209. Num: gofakeit.Number(1, 20),
  2210. }
  2211. estimate.Transaction = []string{"Purchase", "Refinance"}[gofakeit.Number(0, 1)]
  2212. estimate.Price = gofakeit.Number(50000, 200000000)
  2213. estimate.Property =
  2214. propertyTypes[gofakeit.Number(0, len(propertyTypes) - 1)]
  2215. estimate.Occupancy =
  2216. []string{"Primary", "Secondary", "Investment"}[gofakeit.Number(0, 2)]
  2217. estimate.Zip = gofakeit.Zip()
  2218. lsize := gofakeit.Number(1, 6)
  2219. for j := 0; j < lsize; j++ {
  2220. l.Type = ltypes[gofakeit.Number(0, len(ltypes) - 1)]
  2221. l.Amount = gofakeit.Number(
  2222. int(float32(estimate.Price)*0.5),
  2223. int(float32(estimate.Price)*0.93))
  2224. l.Term = gofakeit.Number(4, 30)
  2225. l.Hoi = gofakeit.Number(50000, 700000)
  2226. l.Hazard = gofakeit.Number(5000, 200000)
  2227. l.Tax = gofakeit.Number(5000, 200000)
  2228. l.Interest = gofakeit.Float32Range(0.5, 8)
  2229. l.Fees = generateFees(l)
  2230. l.Credits = generateCredits(l)
  2231. l.Name = gofakeit.AdjectiveDescriptive()
  2232. estimate.Loans = append(estimate.Loans, l)
  2233. }
  2234. estimates = append(estimates, estimate)
  2235. }
  2236. estimates[0].User = users[0].Id
  2237. estimates[1].User = users[0].Id
  2238. for i := range estimates {
  2239. err = estimates[i].insertEstimate(db)
  2240. if err != nil {log.Println(err); return estimates}
  2241. }
  2242. return estimates
  2243. }
  2244. func seedResults(db *sql.DB, estimates []Estimate) error {
  2245. var err error
  2246. for i := range estimates {
  2247. estimates[i].makeResults()
  2248. err = estimates[i].insertResults(db)
  2249. if err != nil {log.Println(err); return err}
  2250. }
  2251. return nil
  2252. }
  2253. func dbSeed(db *sql.DB) {
  2254. addresses := seedAddresses(db)
  2255. branches := seedBranches(db, addresses)
  2256. users := seedUsers(db, addresses, branches)
  2257. _ = seedLicenses(db, users)
  2258. loantypes := seedLoanTypes(db)
  2259. estimates := seedEstimates(db, users, loantypes)
  2260. _ = seedResults(db, estimates)
  2261. }
  2262. func dev(args []string) {
  2263. os.Setenv("DBName", "skouter_dev")
  2264. os.Setenv("DBUser", "tester")
  2265. os.Setenv("DBPass", "test123")
  2266. db, err := sql.Open("mysql",
  2267. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s?multiStatements=true",
  2268. os.Getenv("DBUser"),
  2269. os.Getenv("DBPass"),
  2270. os.Getenv("DBName"),
  2271. ))
  2272. err = db.Ping()
  2273. if err != nil {
  2274. log.Println("Bad database configuration: %v", err)
  2275. panic(err)
  2276. // maybe os.Exit(1) instead
  2277. }
  2278. if len(args) == 0 {
  2279. serve()
  2280. return
  2281. }
  2282. switch args[0] {
  2283. case "seed":
  2284. dbSeed(db)
  2285. case "reset":
  2286. dbReset(db)
  2287. default:
  2288. return
  2289. }
  2290. db.Close()
  2291. }
  2292. func check(args []string) {
  2293. os.Setenv("DBName", "skouter_dev")
  2294. os.Setenv("DBUser", "tester")
  2295. os.Setenv("DBPass", "test123")
  2296. files := http.FileServer(http.Dir(""))
  2297. http.Handle("/assets/", files)
  2298. http.HandleFunc("/", checkPdf)
  2299. log.Fatal(http.ListenAndServe(address, nil))
  2300. }
  2301. func main() {
  2302. if len(os.Args) <= 1 {
  2303. serve()
  2304. return
  2305. }
  2306. switch os.Args[1] {
  2307. case "dev":
  2308. dev(os.Args[2:])
  2309. case "checkpdf":
  2310. check(os.Args[2:])
  2311. default:
  2312. return
  2313. }
  2314. }