Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

skouter.go 41 KiB

hace 2 años
hace 2 años
hace 2 años
hace 2 años
hace 2 años
hace 2 años
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898
  1. package main
  2. import (
  3. "net/http"
  4. "net/mail"
  5. "log"
  6. "sync"
  7. "regexp"
  8. "html/template"
  9. "database/sql"
  10. _ "github.com/go-sql-driver/mysql"
  11. "fmt"
  12. "encoding/json"
  13. "strconv"
  14. "bytes"
  15. "time"
  16. "errors"
  17. "strings"
  18. "math"
  19. "io"
  20. // pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
  21. "github.com/golang-jwt/jwt/v4"
  22. "github.com/disintegration/gift"
  23. "image"
  24. "image/png"
  25. _ "image/jpeg"
  26. )
  27. type User struct {
  28. Id int `json:"id"`
  29. Email string `json:"email"`
  30. FirstName string `json:"firstName"`
  31. LastName string `json:"lastName"`
  32. BranchId int `json:"branchId"`
  33. Status string `json:"status"`
  34. Country string `json:"country"`
  35. Title string `json:"title"`
  36. Verified bool `json:"verified"`
  37. Role string `json:"role"`
  38. Password string `json:"password,omitempty"`
  39. }
  40. type UserClaims struct {
  41. Id int `json:"id"`
  42. Role string `json:"role"`
  43. Exp string `json:"exp"`
  44. }
  45. type Page struct {
  46. tpl *template.Template
  47. Title string
  48. Name string
  49. }
  50. type Borrower struct {
  51. Id int `json:"id"`
  52. Credit int `json:"credit"`
  53. Income int `json:"income"`
  54. Num int `json:"num"`
  55. }
  56. type FeeTemplate struct {
  57. Id int `json:"id"`
  58. User int `json:"user"`
  59. Branch int `json:"branch"`
  60. Amount int `json:"amount"`
  61. Perc float32 `json:"perc"`
  62. Type string `json:"type"`
  63. Notes string `json:"notes"`
  64. Name string `json:"name"`
  65. Category string `json:"category"`
  66. Auto bool `json:"auto"`
  67. }
  68. type Fee struct {
  69. Id int `json:"id"`
  70. LoanId int `json:"loan_id"`
  71. Amount int `json:"amount"`
  72. Perc float32 `json:"perc"`
  73. Type string `json:"type"`
  74. Notes string `json:"notes"`
  75. Name string `json:"name"`
  76. Category string `json:"category"`
  77. }
  78. type LoanType struct {
  79. Id int `json:"id"`
  80. User int `json:"user"`
  81. Branch int `json:"branch"`
  82. Name string `json:"name"`
  83. }
  84. type Loan struct {
  85. Id int `json:"id"`
  86. EstimateId int `json:"estimateId"`
  87. Type LoanType `json:"type"`
  88. Amount int `json:"amount"`
  89. Amortization string `json:"amortization"`
  90. Term int `json:"term"`
  91. Ltv float32 `json:"ltv"`
  92. Dti float32 `json:"dti"`
  93. Hoi int `json:"hoi"`
  94. Hazard int `json:"hazard"`
  95. Tax int `json:"tax"`
  96. Interest float32 `json:"interest"`
  97. Mi MI `json:"mi"`
  98. Fees []Fee `json:"fees"`
  99. Name string `json:"title"`
  100. }
  101. type MI struct {
  102. Type string `json:"user"`
  103. Label string `json:"label"`
  104. Lender string `json:"lender"`
  105. Rate float32 `json:"rate"`
  106. Premium float32 `json:"premium"`
  107. Upfront float32 `json:"upfront"`
  108. Monthly bool `json:"monthly"`
  109. FiveYearTotal float32 `json:"fiveYearTotal"`
  110. InitialAllInPremium float32 `json:"initialAllInPremium"`
  111. InitialAllInRate float32 `json:"initialAllInRate"`
  112. InitialAmount float32 `json:"initialAmount"`
  113. }
  114. type Result struct {
  115. Id int `json:"id"`
  116. LoanId int `json:"loanId"`
  117. LoanPayment int `json:"loanPayment"`
  118. TotalMonthly int `json:"totalMonthly"`
  119. TotalFees int `json:"totalFees"`
  120. TotalCredits int `json:"totalCredits"`
  121. CashToClose int `json:"cashToClose"`
  122. }
  123. type Estimate struct {
  124. Id int `json:"id"`
  125. User int `json:"user"`
  126. Borrower Borrower `json:"borrower"`
  127. Transaction string `json:"transaction"`
  128. Price int `json:"price"`
  129. Property string `json:"property"`
  130. Occupancy string `json:"occupancy"`
  131. Zip string `json:"zip"`
  132. Pud bool `json:"pud"`
  133. Loans []Loan `json:"loans"`
  134. Results []Result `json:"results"`
  135. }
  136. type Password struct {
  137. Old string `json:"old"`
  138. New string `json:"new"`
  139. }
  140. type Endpoint func (http.ResponseWriter, *sql.DB, *http.Request)
  141. var (
  142. regexen = make(map[string]*regexp.Regexp)
  143. relock sync.Mutex
  144. address = "127.0.0.1:8001"
  145. )
  146. var paths = map[string]string {
  147. "home": "views/home.tpl",
  148. "terms": "views/terms.tpl",
  149. "app": "views/app.tpl",
  150. "test": "views/test.tpl",
  151. }
  152. var pages = map[string]Page {
  153. "home": cache("home", "Home"),
  154. "terms": cache("terms", "Terms and Conditions"),
  155. "test": cache("test", "PDF test"),
  156. "app": cache("app", "App"),
  157. }
  158. var roles = map[string]int{
  159. "User": 1,
  160. "Manager": 2,
  161. "Admin": 3,
  162. }
  163. // Used to validate claim in JWT token body. Checks if user id is greater than
  164. // zero and time format is valid
  165. func (c UserClaims) Valid() error {
  166. if c.Id < 1 { return errors.New("Invalid id") }
  167. t, err := time.Parse(time.UnixDate, c.Exp)
  168. if err != nil { return err }
  169. if t.Before(time.Now()) { return errors.New("Token expired.") }
  170. return err
  171. }
  172. func cache(name string, title string) Page {
  173. var p = []string{"views/master.tpl", paths[name]}
  174. tpl := template.Must(template.ParseFiles(p...))
  175. return Page{tpl: tpl,
  176. Title: title,
  177. Name: name,
  178. }
  179. }
  180. func (page Page) Render(w http.ResponseWriter) {
  181. err := page.tpl.Execute(w, page)
  182. if err != nil {
  183. log.Print(err)
  184. }
  185. }
  186. func match(path, pattern string, args *[]string) bool {
  187. relock.Lock()
  188. defer relock.Unlock()
  189. regex := regexen[pattern]
  190. if regex == nil {
  191. regex = regexp.MustCompile("^" + pattern + "$")
  192. regexen[pattern] = regex
  193. }
  194. matches := regex.FindStringSubmatch(path)
  195. if len(matches) <= 0 {
  196. return false
  197. }
  198. *args = matches[1:]
  199. return true
  200. }
  201. func makeResults(estimate Estimate) ([]Result) {
  202. var results []Result
  203. amortize := func(principle float64, rate float64, periods float64) int {
  204. exp := math.Pow(1+rate, periods)
  205. return int(principle * rate * exp / (exp - 1))
  206. }
  207. for _, loan := range estimate.Loans {
  208. var result Result
  209. // Monthly payments use amortized loan payment formula plus monthly MI,
  210. // plus all other recurring fees
  211. result.LoanPayment = amortize(float64(loan.Amount),
  212. float64(loan.Interest / 100 / 12),
  213. float64(loan.Term * 12))
  214. result.TotalMonthly = result.LoanPayment + loan.Hoi + loan.Tax + loan.Hazard
  215. if loan.Mi.Monthly {
  216. result.TotalMonthly = result.TotalMonthly +
  217. int(loan.Mi.Rate/100*float32(loan.Amount))
  218. }
  219. for i := range loan.Fees {
  220. if loan.Fees[i].Amount > 0 {
  221. result.TotalFees = result.TotalFees + loan.Fees[i].Amount
  222. } else {
  223. result.TotalCredits = result.TotalCredits + loan.Fees[i].Amount
  224. }
  225. }
  226. result.CashToClose =
  227. result.TotalFees + result.TotalCredits + (estimate.Price - loan.Amount)
  228. result.LoanId = loan.Id
  229. results = append(results, result)
  230. }
  231. return results
  232. }
  233. func summarize(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  234. var estimate Estimate
  235. err := json.NewDecoder(r.Body).Decode(&estimate)
  236. if err != nil { http.Error(w, "Invalid estimate.", 422); return }
  237. results := makeResults(estimate)
  238. json.NewEncoder(w).Encode(results)
  239. }
  240. func getLoanType( db *sql.DB, id int) (LoanType, error) {
  241. types, err := getLoanTypes(db, id, 0, 0)
  242. if err != nil { return LoanType{Id: id}, err }
  243. if len(types) == 0 {
  244. return LoanType{Id: id}, errors.New("No type with that id")
  245. }
  246. return types[0], nil
  247. }
  248. func getLoanTypes( db *sql.DB, id int, user int, branch int ) (
  249. []LoanType, error) {
  250. var loans []LoanType
  251. var query = `SELECT
  252. id,
  253. user_id,
  254. branch_id,
  255. name
  256. FROM loan_type WHERE loan_type.id = CASE @e := ? WHEN 0 THEN id ELSE @e END
  257. OR
  258. loan_type.user_id = CASE @e := ? WHEN 0 THEN id ELSE @e END
  259. OR
  260. loan_type.branch_id = CASE @e := ? WHEN 0 THEN id ELSE @e END`
  261. // Should be changed to specify user
  262. rows, err := db.Query(query, id, user, branch)
  263. if err != nil {
  264. return nil, fmt.Errorf("loan_type error: %v", err)
  265. }
  266. defer rows.Close()
  267. for rows.Next() {
  268. var loan LoanType
  269. if err := rows.Scan(
  270. &loan.Id,
  271. &loan.User,
  272. &loan.Branch,
  273. &loan.Name)
  274. err != nil {
  275. log.Printf("Error occured fetching loan: %v", err)
  276. return nil, fmt.Errorf("Error occured fetching loan: %v", err)
  277. }
  278. loans = append(loans, loan)
  279. }
  280. return loans, nil
  281. }
  282. func getFees(db *sql.DB, loan int) ([]Fee, error) {
  283. var fees []Fee
  284. query := `SELECT id, loan_id, amount, perc, type, notes, name, category
  285. FROM fee
  286. WHERE loan_id = ?`
  287. rows, err := db.Query(query, loan)
  288. if err != nil {
  289. return nil, fmt.Errorf("Fee query error %v", err)
  290. }
  291. defer rows.Close()
  292. for rows.Next() {
  293. var fee Fee
  294. if err := rows.Scan(
  295. &fee.Id,
  296. &fee.LoanId,
  297. &fee.Amount,
  298. &fee.Perc,
  299. &fee.Type,
  300. &fee.Notes,
  301. &fee.Name,
  302. &fee.Category,
  303. )
  304. err != nil {
  305. return nil, fmt.Errorf("Fees scanning error: %v", err)
  306. }
  307. fees = append(fees, fee)
  308. }
  309. return fees, nil
  310. }
  311. func fetchFeesTemp(db *sql.DB, user int, branch int) ([]FeeTemplate, error) {
  312. var fees []FeeTemplate
  313. query := `SELECT
  314. id, user_id, COALESCE(branch_id, 0), amount, perc, type, notes, name,
  315. category, auto
  316. FROM fee_template
  317. WHERE user_id = ? OR branch_id = ?
  318. `
  319. rows, err := db.Query(query, user, branch)
  320. if err != nil {
  321. return nil, fmt.Errorf("Fee template query error %v", err)
  322. }
  323. defer rows.Close()
  324. for rows.Next() {
  325. var fee FeeTemplate
  326. if err := rows.Scan(
  327. &fee.Id,
  328. &fee.User,
  329. &fee.Branch,
  330. &fee.Amount,
  331. &fee.Perc,
  332. &fee.Type,
  333. &fee.Notes,
  334. &fee.Name,
  335. &fee.Category,
  336. &fee.Auto)
  337. err != nil {
  338. return nil, fmt.Errorf("FeesTemplate scanning error: %v", err)
  339. }
  340. fees = append(fees, fee)
  341. }
  342. return fees, nil
  343. }
  344. // Fetch fees from the database
  345. func getFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  346. var fees []FeeTemplate
  347. claims, err := getClaims(r)
  348. if err != nil { w.WriteHeader(500); return }
  349. users, err := queryUsers(db, claims.Id)
  350. if err != nil { w.WriteHeader(422); return }
  351. fees, err = fetchFeesTemp(db, claims.Id, users[0].BranchId)
  352. json.NewEncoder(w).Encode(fees)
  353. }
  354. // Fetch fees from the database
  355. func createFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  356. var fee FeeTemplate
  357. var query string
  358. var row *sql.Row
  359. var err error
  360. claims, err := getClaims(r)
  361. // var id int // Inserted estimate's id
  362. err = json.NewDecoder(r.Body).Decode(&fee)
  363. if err != nil { w.WriteHeader(422); return }
  364. query = `INSERT INTO fee_template
  365. (
  366. user_id,
  367. branch_id,
  368. amount,
  369. perc,
  370. type,
  371. notes,
  372. name,
  373. auto
  374. )
  375. VALUES (?, NULL, ?, ?, ?, ?, ?, ?)
  376. RETURNING id
  377. `
  378. row = db.QueryRow(query,
  379. claims.Id,
  380. fee.Amount,
  381. fee.Perc,
  382. fee.Type,
  383. fee.Notes,
  384. fee.Name,
  385. fee.Auto,
  386. )
  387. err = row.Scan(&fee.Id)
  388. if err != nil { w.WriteHeader(500); return }
  389. json.NewEncoder(w).Encode(fee)
  390. }
  391. // Fetch fees from the database
  392. func deleteFeeTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  393. var fee FeeTemplate
  394. var query string
  395. var err error
  396. // claims, err := getClaims(r)
  397. // var id int // Inserted estimate's id
  398. err = json.NewDecoder(r.Body).Decode(&fee)
  399. if err != nil { w.WriteHeader(422); return }
  400. query = `DELETE FROM fee_template WHERE id = ?`
  401. _, err = db.Exec(query, fee.Id)
  402. if err != nil { w.WriteHeader(500); return }
  403. }
  404. func getMi(db *sql.DB, loan int) (MI, error) {
  405. var mi MI
  406. query := `SELECT
  407. type, label, lender, rate, premium, upfront, five_year_total,
  408. initial_premium, initial_rate, initial_amount
  409. FROM mi WHERE loan_id = ?`
  410. rows, err := db.Query(query, loan)
  411. if err != nil { return mi, err }
  412. defer rows.Close()
  413. if (!rows.Next()) { return mi, nil }
  414. if err := rows.Scan(
  415. &mi.Type,
  416. &mi.Label,
  417. &mi.Lender,
  418. &mi.Rate,
  419. &mi.Premium,
  420. &mi.Upfront,
  421. &mi.FiveYearTotal,
  422. &mi.InitialAllInPremium,
  423. &mi.InitialAllInRate,
  424. &mi.InitialAmount,
  425. )
  426. err != nil {
  427. return mi, err
  428. }
  429. return mi, nil
  430. }
  431. func getBorrower(db *sql.DB, id int) (Borrower, error) {
  432. var borrower Borrower
  433. row := db.QueryRow(
  434. "SELECT * FROM borrower " +
  435. "WHERE id = ? LIMIT 1",
  436. id)
  437. if err := row.Scan(
  438. &borrower.Id,
  439. &borrower.Credit,
  440. &borrower.Income,
  441. &borrower.Num,
  442. )
  443. err != nil {
  444. return borrower, fmt.Errorf("Borrower scanning error: %v", err)
  445. }
  446. return borrower, nil
  447. }
  448. // Query Lender APIs and parse responses into MI structs
  449. func fetchMi(db *sql.DB, estimate *Estimate, pos int) []MI {
  450. var err error
  451. var loan Loan = estimate.Loans[pos]
  452. var ltv = func(l float32) string {
  453. switch {
  454. case l > 95: return "LTV97"
  455. case l > 90: return "LTV95"
  456. case l > 85: return "LTV90"
  457. default: return "LTV85"
  458. }
  459. }
  460. var term = func(t int) string {
  461. switch {
  462. case t <= 10: return "A10"
  463. case t <= 15: return "A15"
  464. case t <= 20: return "A20"
  465. case t <= 25: return "A25"
  466. case t <= 30: return "A30"
  467. default: return "A40"
  468. }
  469. }
  470. var propertyCodes = map[string]string {
  471. "Single Attached": "SFO",
  472. "Single Detached": "SFO",
  473. "Condo Lo-rise": "CON",
  474. "Condo Hi-rise": "CON",
  475. }
  476. var purposeCodes = map[string]string {
  477. "Purchase": "PUR",
  478. "Refinance": "RRT",
  479. }
  480. body, err := json.Marshal(map[string]any{
  481. "zipCode": estimate.Zip,
  482. "stateCode": "CA",
  483. "address": "",
  484. "propertyTypeCode": propertyCodes[estimate.Property],
  485. "occupancyTypeCode": "PRS",
  486. "loanPurposeCode": purposeCodes[estimate.Transaction],
  487. "loanAmount": loan.Amount,
  488. "loanToValue": ltv(loan.Ltv),
  489. "amortizationTerm": term(loan.Term),
  490. "loanTypeCode": "FXD",
  491. "duLpDecisionCode": "DAE",
  492. "loanProgramCodes": []any{},
  493. "debtToIncome": loan.Dti,
  494. "wholesaleLoan": 0,
  495. "coveragePercentageCode": "L30",
  496. "productCode": "BPM",
  497. "renewalTypeCode": "CON",
  498. "numberOfBorrowers": 1,
  499. "coBorrowerCreditScores": []any{},
  500. "borrowerCreditScore": strconv.Itoa(estimate.Borrower.Credit),
  501. "masterPolicy": nil,
  502. "selfEmployedIndicator": false,
  503. "armType": "",
  504. "userId": 44504,
  505. })
  506. if err != nil {
  507. log.Printf("Could not marshal NationalMI body: \n%v\n%v\n",
  508. bytes.NewBuffer(body), err)
  509. }
  510. req, err := http.NewRequest("POST",
  511. "https://rate-gps.nationalmi.com/rates/productRateQuote",
  512. bytes.NewBuffer(body))
  513. req.Header.Add("Content-Type", "application/json")
  514. req.AddCookie(&http.Cookie{
  515. Name: "nmirategps_email",
  516. Value: config["NationalMIEmail"]})
  517. resp, err := http.DefaultClient.Do(req)
  518. var res map[string]interface{}
  519. var result []MI
  520. if resp.StatusCode != 200 {
  521. log.Printf("the status: %v\nthe resp: %v\n the req: %v\n the body: %v\n",
  522. resp.Status, resp, req.Body, bytes.NewBuffer(body))
  523. } else {
  524. json.NewDecoder(resp.Body).Decode(&res)
  525. // estimate.Loans[pos].Mi = res
  526. // Parse res into result here
  527. }
  528. return result
  529. }
  530. // Make comparison PDF
  531. func generatePDF(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  532. }
  533. func login(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  534. var id int
  535. var role string
  536. var err error
  537. var user User
  538. json.NewDecoder(r.Body).Decode(&user)
  539. row := db.QueryRow(
  540. `SELECT id, role FROM user WHERE email = ? AND password = sha2(?, 256)`,
  541. user.Email, user.Password,
  542. )
  543. err = row.Scan(&id, &role)
  544. if err != nil {
  545. http.Error(w, "Invalid Credentials.", http.StatusUnauthorized)
  546. return
  547. }
  548. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  549. UserClaims{ Id: id, Role: role,
  550. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  551. tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
  552. if err != nil {
  553. log.Println("Token could not be signed: ", err, tokenStr)
  554. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  555. return
  556. }
  557. cookie := http.Cookie{Name: "skouter",
  558. Value: tokenStr,
  559. Path: "/",
  560. Expires: time.Now().Add(time.Hour * 24)}
  561. http.SetCookie(w, &cookie)
  562. _, err = w.Write([]byte(tokenStr))
  563. if err != nil {
  564. http.Error(w,
  565. "Could not complete token write.",
  566. http.StatusInternalServerError)}
  567. }
  568. func getToken(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  569. claims, err := getClaims(r)
  570. // Will verify existing signature and expiry time
  571. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  572. UserClaims{ Id: claims.Id, Role: claims.Role,
  573. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  574. tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
  575. if err != nil {
  576. log.Println("Token could not be signed: ", err, tokenStr)
  577. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  578. return
  579. }
  580. cookie := http.Cookie{Name: "skouter",
  581. Value: tokenStr,
  582. Path: "/",
  583. Expires: time.Now().Add(time.Hour * 24)}
  584. http.SetCookie(w, &cookie)
  585. _, err = w.Write([]byte(tokenStr))
  586. if err != nil {
  587. http.Error(w,
  588. "Could not complete token write.",
  589. http.StatusInternalServerError)}
  590. }
  591. func getClaims(r *http.Request) (UserClaims, error) {
  592. claims := new(UserClaims)
  593. _, tokenStr, found := strings.Cut(r.Header.Get("Authorization"), " ")
  594. if !found {
  595. return *claims, errors.New("Token not found")
  596. }
  597. // Pull token payload into UserClaims
  598. _, err := jwt.ParseWithClaims(tokenStr, claims,
  599. func(token *jwt.Token) (any, error) {
  600. return []byte(config["JWT_SECRET"]), nil
  601. })
  602. if err != nil {
  603. return *claims, err
  604. }
  605. if err = claims.Valid(); err != nil {
  606. return *claims, err
  607. }
  608. return *claims, nil
  609. }
  610. func guard(r *http.Request, required int) bool {
  611. claims, err := getClaims(r)
  612. if err != nil { return false }
  613. if roles[claims.Role] < required { return false }
  614. return true
  615. }
  616. func queryUsers(db *sql.DB, id int) ( []User, error ) {
  617. var users []User
  618. var query string
  619. var rows *sql.Rows
  620. var err error
  621. query = `SELECT
  622. u.id,
  623. u.email,
  624. u.first_name,
  625. u.last_name,
  626. u.branch_id,
  627. u.country,
  628. u.title,
  629. u.status,
  630. u.verified,
  631. u.role
  632. FROM user u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
  633. `
  634. rows, err = db.Query(query, id)
  635. if err != nil {
  636. return users, err
  637. }
  638. defer rows.Close()
  639. for rows.Next() {
  640. var user User
  641. if err := rows.Scan(
  642. &user.Id,
  643. &user.Email,
  644. &user.FirstName,
  645. &user.LastName,
  646. &user.BranchId,
  647. &user.Country,
  648. &user.Title,
  649. &user.Status,
  650. &user.Verified,
  651. &user.Role,
  652. )
  653. err != nil {
  654. return users, err
  655. }
  656. users = append(users, user)
  657. }
  658. // Prevents runtime panics
  659. if len(users) == 0 { return users, errors.New("User not found.") }
  660. return users, nil
  661. }
  662. func insertResults(db *sql.DB, results []Result) (error){
  663. var query string
  664. var row *sql.Row
  665. var err error
  666. query = `INSERT INTO estimate_result
  667. (
  668. loan_id,
  669. loan_payment,
  670. total_monthly,
  671. total_fees,
  672. total_credits,
  673. cash_to_close
  674. )
  675. VALUES (?, ?, ?, ?, ?, ?, ?)
  676. RETURNING id
  677. `
  678. for i := range results {
  679. db.QueryRow(query,
  680. results[i].LoanId,
  681. results[i].LoanPayment,
  682. results[i].TotalMonthly,
  683. results[i].TotalFees,
  684. results[i].TotalCredits,
  685. results[i].CashToClose,
  686. )
  687. err = row.Scan(&results[i].Id)
  688. if err != nil { return err }
  689. }
  690. return nil
  691. }
  692. func insertUser(db *sql.DB, user User) (User, error){
  693. var query string
  694. var row *sql.Row
  695. var err error
  696. var id int // Inserted user's id
  697. query = `INSERT INTO user
  698. (
  699. email,
  700. first_name,
  701. last_name,
  702. password,
  703. created,
  704. role,
  705. verified,
  706. last_login
  707. )
  708. VALUES (?, ?, ?, sha2(?, 256), NOW(), ?, ?, NOW())
  709. RETURNING id
  710. `
  711. row = db.QueryRow(query,
  712. user.Email,
  713. user.FirstName,
  714. user.LastName,
  715. user.Password,
  716. user.Role,
  717. user.Verified,
  718. )
  719. err = row.Scan(&id)
  720. if err != nil { return User{}, err }
  721. users, err := queryUsers(db, id)
  722. if err != nil { return User{}, err }
  723. return users[0], nil
  724. }
  725. func updateUser(user User, db *sql.DB) error {
  726. query := `
  727. UPDATE user
  728. SET
  729. email = CASE @e := ? WHEN '' THEN email ELSE @e END,
  730. first_name = CASE @fn := ? WHEN '' THEN first_name ELSE @fn END,
  731. last_name = CASE @ln := ? WHEN '' THEN last_name ELSE @ln END,
  732. role = CASE @r := ? WHEN '' THEN role ELSE @r END,
  733. password = CASE @p := ? WHEN '' THEN password ELSE sha2(@p, 256) END
  734. WHERE id = ?
  735. `
  736. _, err := db.Exec(query,
  737. user.Email,
  738. user.FirstName,
  739. user.LastName,
  740. user.Role,
  741. user.Password,
  742. user.Id,
  743. )
  744. return err
  745. }
  746. func getUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  747. claims, err := getClaims(r)
  748. if err != nil { w.WriteHeader(500); return }
  749. users, err := queryUsers(db, claims.Id)
  750. if err != nil { w.WriteHeader(422); log.Println(err); return }
  751. json.NewEncoder(w).Encode(users)
  752. }
  753. func getUsers(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  754. users, err := queryUsers(db, 0)
  755. if err != nil {
  756. w.WriteHeader(http.StatusInternalServerError)
  757. return
  758. }
  759. json.NewEncoder(w).Encode(users)
  760. }
  761. // Updates a user using only specified values in the JSON body
  762. func setUser(user User, db *sql.DB) error {
  763. _, err := mail.ParseAddress(user.Email)
  764. if err != nil { return err }
  765. if roles[user.Role] == 0 {
  766. return errors.New("Invalid role")
  767. }
  768. err = updateUser(user, db)
  769. if err != nil { return err }
  770. return nil
  771. }
  772. func patchUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  773. var user User
  774. err := json.NewDecoder(r.Body).Decode(&user)
  775. if err != nil { http.Error(w, "Invalid fields", 422); return }
  776. err = setUser(user, db)
  777. if err != nil { http.Error(w, err.Error(), 422); return }
  778. }
  779. // Update specified fields of the user specified in the claim
  780. func patchSelf(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  781. claim, err := getClaims(r)
  782. var user User
  783. json.NewDecoder(r.Body).Decode(&user)
  784. // First check that the target user to be updated is the same as the claim id, and
  785. // their role is unchanged.
  786. if err != nil || claim.Id != user.Id {
  787. http.Error(w, "Target user's id does not match claim.", 401)
  788. return
  789. }
  790. if claim.Role != user.Role && user.Role != "" {
  791. http.Error(w, "Administrator required to escalate role.", 401)
  792. return
  793. }
  794. err = setUser(user, db)
  795. if err != nil { http.Error(w, err.Error(), 422); return }
  796. }
  797. func deleteUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  798. var user User
  799. err := json.NewDecoder(r.Body).Decode(&user)
  800. if err != nil {
  801. http.Error(w, "Invalid fields.", 422)
  802. return
  803. }
  804. query := `DELETE FROM user WHERE id = ?`
  805. _, err = db.Exec(query, user.Id)
  806. if err != nil {
  807. http.Error(w, "Could not delete.", 500)
  808. }
  809. }
  810. func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  811. var user User
  812. err := json.NewDecoder(r.Body).Decode(&user)
  813. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  814. _, err = mail.ParseAddress(user.Email)
  815. if err != nil { http.Error(w, "Invalid email.", 422); return }
  816. if roles[user.Role] == 0 {
  817. http.Error(w, "Invalid role.", 422)
  818. }
  819. user, err = insertUser(db, user)
  820. if err != nil { http.Error(w, "Error creating user.", 422); return }
  821. json.NewEncoder(w).Encode(user)
  822. }
  823. func checkPassword(db *sql.DB, id int, pass string) bool {
  824. var p string
  825. query := `SELECT
  826. password
  827. FROM user WHERE user.id = ? AND password = sha2(?, 256)
  828. `
  829. row := db.QueryRow(query, id, pass)
  830. err := row.Scan(&p)
  831. if err != nil { return false }
  832. return true
  833. }
  834. func setPassword(db *sql.DB, id int, pass string) error {
  835. query := `UPDATE user
  836. SET password = sha2(?, 256)
  837. WHERE user.id = ?
  838. `
  839. _, err := db.Exec(query, pass, id)
  840. if err != nil { return errors.New("Could not insert password.") }
  841. return nil
  842. }
  843. func changePassword(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  844. var pass Password
  845. claim, err := getClaims(r)
  846. err = json.NewDecoder(r.Body).Decode(&pass)
  847. if err != nil { http.Error(w, "Bad fields.", 422); return }
  848. fmt.Println(pass)
  849. if checkPassword(db, claim.Id, pass.Old) {
  850. err = setPassword(db, claim.Id, pass.New)
  851. } else {
  852. http.Error(w, "Incorrect old password.", 401)
  853. return
  854. }
  855. if err != nil { http.Error(w, err.Error(), 500); return }
  856. }
  857. func fetchAvatar(db *sql.DB, user int) ( []byte, error ) {
  858. var img []byte
  859. var query string
  860. var err error
  861. query = `SELECT
  862. avatar
  863. FROM user WHERE user.id = ?
  864. `
  865. row := db.QueryRow(query, user)
  866. err = row.Scan(&img)
  867. if err != nil {
  868. return img, err
  869. }
  870. return img, nil
  871. }
  872. func insertAvatar(db *sql.DB, user int, img []byte) error {
  873. query := `UPDATE user
  874. SET avatar = ?
  875. WHERE id = ?
  876. `
  877. _, err := db.Exec(query, img, user)
  878. if err != nil {
  879. return err
  880. }
  881. return nil
  882. }
  883. func fetchLetterhead(db *sql.DB, user int) ( []byte, error ) {
  884. var img []byte
  885. var query string
  886. var err error
  887. query = `SELECT
  888. letterhead
  889. FROM user WHERE user.id = ?
  890. `
  891. row := db.QueryRow(query, user)
  892. err = row.Scan(&img)
  893. if err != nil {
  894. return img, err
  895. }
  896. return img, nil
  897. }
  898. func insertLetterhead(db *sql.DB, user int, img []byte) error {
  899. query := `UPDATE user
  900. SET letterhead = ?
  901. WHERE id = ?
  902. `
  903. _, err := db.Exec(query, img, user)
  904. if err != nil {
  905. return err
  906. }
  907. return nil
  908. }
  909. func setAvatar(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  910. var validTypes []string = []string{"image/png", "image/jpeg"}
  911. var isValidType bool
  912. claims, err := getClaims(r)
  913. if err != nil { http.Error(w, "Invalid token.", 422); return }
  914. img, err := io.ReadAll(r.Body)
  915. if err != nil { http.Error(w, "Invalid file.", 422); return }
  916. for _, v := range validTypes {
  917. if v == http.DetectContentType(img) { isValidType = true }
  918. }
  919. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  920. err = insertAvatar(db, claims.Id, img)
  921. if err != nil { http.Error(w, "Could not insert.", 500); return }
  922. }
  923. func getAvatar(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  924. claims, err := getClaims(r)
  925. if err != nil { http.Error(w, "Invalid token.", 422); return }
  926. img, err := fetchAvatar(db, claims.Id)
  927. if err != nil { http.Error(w, "Could not retrieve.", 500); return }
  928. w.Header().Set("Content-Type", http.DetectContentType(img))
  929. w.Write(img)
  930. }
  931. func setLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  932. var validTypes []string = []string{"image/png", "image/jpeg"}
  933. var isValidType bool
  934. claims, err := getClaims(r)
  935. if err != nil { http.Error(w, "Invalid token.", 422); return }
  936. img, err := io.ReadAll(r.Body)
  937. if err != nil { http.Error(w, "Invalid file.", 422); return }
  938. for _, v := range validTypes {
  939. if v == http.DetectContentType(img) { isValidType = true }
  940. }
  941. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  942. err = insertLetterhead(db, claims.Id, img)
  943. if err != nil { http.Error(w, "Could not insert.", 500); return }
  944. }
  945. func getLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  946. claims, err := getClaims(r)
  947. if err != nil { http.Error(w, "Invalid token.", 422); return }
  948. img, err := fetchLetterhead(db, claims.Id)
  949. if err != nil { http.Error(w, "Could not retrieve.", 500); return }
  950. w.Header().Set("Content-Type", http.DetectContentType(img))
  951. w.Write(img)
  952. }
  953. func queryBorrower(db *sql.DB, id int) ( Borrower, error ) {
  954. var borrower Borrower
  955. var query string
  956. var err error
  957. query = `SELECT
  958. l.id,
  959. l.credit_score,
  960. l.num,
  961. l.monthly_income
  962. FROM borrower l WHERE l.id = ?
  963. `
  964. row := db.QueryRow(query, id)
  965. err = row.Scan(
  966. borrower.Id,
  967. borrower.Credit,
  968. borrower.Num,
  969. borrower.Income,
  970. )
  971. if err != nil {
  972. return borrower, err
  973. }
  974. return borrower, nil
  975. }
  976. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  977. func getResults(db *sql.DB, e int, id int) ( []Result, error ) {
  978. var results []Result
  979. var query string
  980. var rows *sql.Rows
  981. var err error
  982. query = `SELECT
  983. r.id,
  984. loan_id,
  985. loan_payment,
  986. total_monthly,
  987. total_fees,
  988. total_credits,
  989. cash_to_close
  990. FROM estimate_result r
  991. INNER JOIN loan
  992. ON r.loan_id = loan.id
  993. WHERE r.id = CASE @e := ? WHEN 0 THEN r.id ELSE @e END
  994. AND loan.estimate_id = ?
  995. `
  996. rows, err = db.Query(query, id, e)
  997. if err != nil {
  998. return results, err
  999. }
  1000. defer rows.Close()
  1001. for rows.Next() {
  1002. var result Result
  1003. if err := rows.Scan(
  1004. &result.Id,
  1005. &result.LoanId,
  1006. &result.LoanPayment,
  1007. &result.TotalMonthly,
  1008. &result.TotalFees,
  1009. &result.TotalCredits,
  1010. &result.CashToClose,
  1011. )
  1012. err != nil {
  1013. return results, err
  1014. }
  1015. results = append(results, result)
  1016. }
  1017. // Prevents runtime panics
  1018. // if len(results) == 0 { return results, errors.New("Result not found.") }
  1019. return results, nil
  1020. }
  1021. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  1022. func getLoans(db *sql.DB, e int, id int) ( []Loan, error ) {
  1023. var loans []Loan
  1024. var query string
  1025. var rows *sql.Rows
  1026. var err error
  1027. query = `SELECT
  1028. l.id,
  1029. l.type_id,
  1030. l.estimate_id,
  1031. l.amount,
  1032. l.term,
  1033. l.interest,
  1034. l.ltv,
  1035. l.dti,
  1036. l.hoi,
  1037. l.tax,
  1038. l.name
  1039. FROM loan l WHERE l.id = CASE @e := ? WHEN 0 THEN l.id ELSE @e END AND
  1040. l.estimate_id = ?
  1041. `
  1042. rows, err = db.Query(query, id, e)
  1043. if err != nil {
  1044. return loans, err
  1045. }
  1046. defer rows.Close()
  1047. for rows.Next() {
  1048. var loan Loan
  1049. if err := rows.Scan(
  1050. &loan.Id,
  1051. &loan.Type.Id,
  1052. &loan.EstimateId,
  1053. &loan.Amount,
  1054. &loan.Term,
  1055. &loan.Interest,
  1056. &loan.Ltv,
  1057. &loan.Dti,
  1058. &loan.Hoi,
  1059. &loan.Tax,
  1060. &loan.Name,
  1061. )
  1062. err != nil {
  1063. return loans, err
  1064. }
  1065. mi, err := getMi(db, loan.Id)
  1066. if err != nil {
  1067. return loans, err
  1068. }
  1069. loan.Mi = mi
  1070. fees, err := getFees(db, loan.Id)
  1071. if err != nil {
  1072. return loans, err
  1073. }
  1074. loan.Fees = fees
  1075. loan.Type, err = getLoanType(db, loan.Type.Id)
  1076. if err != nil {
  1077. return loans, err
  1078. }
  1079. loans = append(loans, loan)
  1080. }
  1081. // Prevents runtime panics
  1082. if len(loans) == 0 { return loans, errors.New("Loan not found.") }
  1083. return loans, nil
  1084. }
  1085. func getEstimates(db *sql.DB, id int, user int) ( []Estimate, error ) {
  1086. var estimates []Estimate
  1087. var query string
  1088. var rows *sql.Rows
  1089. var err error
  1090. query = `SELECT
  1091. id,
  1092. user_id,
  1093. borrower_id,
  1094. transaction,
  1095. price,
  1096. property,
  1097. occupancy,
  1098. zip,
  1099. pud
  1100. FROM estimate WHERE id = CASE @e := ? WHEN 0 THEN id ELSE @e END AND
  1101. user_id = CASE @e := ? WHEN 0 THEN user_id ELSE @e END
  1102. `
  1103. rows, err = db.Query(query, id, user)
  1104. if err != nil {
  1105. return estimates, err
  1106. }
  1107. defer rows.Close()
  1108. for rows.Next() {
  1109. var estimate Estimate
  1110. if err := rows.Scan(
  1111. &estimate.Id,
  1112. &estimate.User,
  1113. &estimate.Borrower.Id,
  1114. &estimate.Transaction,
  1115. &estimate.Price,
  1116. &estimate.Property,
  1117. &estimate.Occupancy,
  1118. &estimate.Zip,
  1119. &estimate.Pud,
  1120. )
  1121. err != nil {
  1122. return estimates, err
  1123. }
  1124. borrower, err := getBorrower(db, estimate.Borrower.Id)
  1125. if err != nil {
  1126. return estimates, err
  1127. }
  1128. estimate.Borrower = borrower
  1129. estimate.Results, err = getResults(db, estimate.Id, 0)
  1130. if err != nil {
  1131. return estimates, err
  1132. }
  1133. estimates = append(estimates, estimate)
  1134. }
  1135. // Prevents runtime panics
  1136. if len(estimates) == 0 { return estimates, errors.New("Estimate not found.") }
  1137. for i := range estimates {
  1138. estimates[i].Loans, err = getLoans(db, estimates[i].Id, 0)
  1139. if err != nil { return estimates, err }
  1140. }
  1141. return estimates, nil
  1142. }
  1143. // Accepts a borrower struct and returns the id of the inserted borrower and
  1144. // any related error.
  1145. func insertBorrower(db *sql.DB, borrower Borrower) (int, error) {
  1146. var query string
  1147. var row *sql.Row
  1148. var err error
  1149. var id int // Inserted loan's id
  1150. query = `INSERT INTO borrower
  1151. (
  1152. credit_score,
  1153. monthly_income,
  1154. num
  1155. )
  1156. VALUES (?, ?, ?)
  1157. RETURNING id
  1158. `
  1159. row = db.QueryRow(query,
  1160. borrower.Credit,
  1161. borrower.Income,
  1162. borrower.Num,
  1163. )
  1164. err = row.Scan(&id)
  1165. if err != nil { return 0, err }
  1166. return id, nil
  1167. }
  1168. func insertMi(db *sql.DB, mi MI) (int, error) {
  1169. var id int
  1170. query := `INSERT INTO mi
  1171. (
  1172. type,
  1173. label,
  1174. lender,
  1175. rate,
  1176. premium,
  1177. upfront,
  1178. five_year_total,
  1179. initial_premium,
  1180. initial_rate,
  1181. initial_amount
  1182. )
  1183. VALUES (?, ?, ?, ?, ?, ?, ?)
  1184. RETURNING id`
  1185. row := db.QueryRow(query,
  1186. &mi.Type,
  1187. &mi.Label,
  1188. &mi.Lender,
  1189. &mi.Rate,
  1190. &mi.Premium,
  1191. &mi.Upfront,
  1192. &mi.FiveYearTotal,
  1193. &mi.InitialAllInPremium,
  1194. &mi.InitialAllInRate,
  1195. &mi.InitialAmount,
  1196. )
  1197. err := row.Scan(&id)
  1198. if err != nil { return 0, err }
  1199. return id, nil
  1200. }
  1201. func insertFee(db *sql.DB, fee Fee) (int, error) {
  1202. var id int
  1203. query := `INSERT INTO fee
  1204. (loan_id, amount, perc, type, notes, name, category)
  1205. VALUES (?, ?, ?, ?, ?, ?, ?)
  1206. RETURNING id`
  1207. row := db.QueryRow(query,
  1208. fee.LoanId,
  1209. fee.Amount,
  1210. fee.Perc,
  1211. fee.Type,
  1212. fee.Notes,
  1213. fee.Name,
  1214. fee.Category,
  1215. )
  1216. err := row.Scan(&id)
  1217. if err != nil { return 0, err }
  1218. return id, nil
  1219. }
  1220. func insertLoan(db *sql.DB, loan Loan) (Loan, error){
  1221. var query string
  1222. var row *sql.Row
  1223. var err error
  1224. var id int // Inserted loan's id
  1225. query = `INSERT INTO loan
  1226. (
  1227. estimate_id,
  1228. type_id,
  1229. amount,
  1230. term,
  1231. interest,
  1232. ltv,
  1233. dti,
  1234. hoi,
  1235. tax,
  1236. name
  1237. )
  1238. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1239. RETURNING id
  1240. `
  1241. row = db.QueryRow(query,
  1242. loan.EstimateId,
  1243. loan.Type.Id,
  1244. loan.Amount,
  1245. loan.Term,
  1246. loan.Interest,
  1247. loan.Ltv,
  1248. loan.Dti,
  1249. loan.Hoi,
  1250. loan.Tax,
  1251. loan.Name,
  1252. )
  1253. err = row.Scan(&id)
  1254. if err != nil { return loan, err }
  1255. _, err = insertMi(db, loan.Mi)
  1256. if err != nil { return loan, err }
  1257. for i := range loan.Fees {
  1258. _, err := insertFee(db, loan.Fees[i])
  1259. if err != nil { return loan, err }
  1260. }
  1261. loans, err := getLoans(db, id, 0)
  1262. if err != nil { return Loan{}, err }
  1263. return loans[0], nil
  1264. }
  1265. func insertEstimate(db *sql.DB, estimate Estimate) (Estimate, error){
  1266. var query string
  1267. var row *sql.Row
  1268. var err error
  1269. // var id int // Inserted estimate's id
  1270. estimate.Borrower.Id, err = insertBorrower(db, estimate.Borrower)
  1271. if err != nil { return Estimate{}, err }
  1272. query = `INSERT INTO estimate
  1273. (
  1274. user_id,
  1275. borrower_id,
  1276. transaction,
  1277. price,
  1278. property,
  1279. occupancy,
  1280. zip,
  1281. pud
  1282. )
  1283. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  1284. RETURNING id
  1285. `
  1286. row = db.QueryRow(query,
  1287. estimate.User,
  1288. estimate.Borrower.Id,
  1289. estimate.Transaction,
  1290. estimate.Price,
  1291. estimate.Property,
  1292. estimate.Occupancy,
  1293. estimate.Zip,
  1294. estimate.Pud,
  1295. )
  1296. err = row.Scan(&estimate.Id)
  1297. if err != nil { return Estimate{}, err }
  1298. for _, l := range estimate.Loans {
  1299. l.EstimateId = estimate.Id
  1300. _, err = insertLoan(db, l)
  1301. if err != nil { return estimate, err }
  1302. }
  1303. estimates, err := getEstimates(db, estimate.Id, 0)
  1304. if err != nil { return Estimate{}, err }
  1305. return estimates[0], nil
  1306. }
  1307. func createEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1308. var estimate Estimate
  1309. err := json.NewDecoder(r.Body).Decode(&estimate)
  1310. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  1311. claims, err := getClaims(r)
  1312. estimate.User = claims.Id
  1313. estimate, err = insertEstimate(db, estimate)
  1314. if err != nil { http.Error(w, err.Error(), 422); return }
  1315. estimate.Results = makeResults(estimate)
  1316. err = insertResults(db, estimate.Results)
  1317. if err != nil { http.Error(w, err.Error(), 500); return }
  1318. e, err := getEstimates(db, estimate.Id, 0)
  1319. if err != nil { http.Error(w, err.Error(), 500); return }
  1320. json.NewEncoder(w).Encode(e[0])
  1321. }
  1322. // Query all estimates that belong to the current user
  1323. func fetchEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1324. var estimates []Estimate
  1325. claims, err := getClaims(r)
  1326. estimates, err = getEstimates(db, 0, claims.Id)
  1327. if err != nil { http.Error(w, err.Error(), 500); return }
  1328. json.NewEncoder(w).Encode(estimates)
  1329. }
  1330. func checkConventional(l Loan, b Borrower) error {
  1331. if b.Credit < 620 {
  1332. return errors.New("Credit score too low for conventional loan")
  1333. }
  1334. // Buyer needs to put down 20% to avoid mortgage insurance
  1335. if (l.Ltv > 80 && l.Mi.Rate == 0) {
  1336. return errors.New(fmt.Sprintln(
  1337. l.Name,
  1338. "down payment must be 20% to avoid insurance",
  1339. ))
  1340. }
  1341. return nil
  1342. }
  1343. func checkFHA(l Loan, b Borrower) error {
  1344. if b.Credit < 500 {
  1345. return errors.New("Credit score too low for FHA loan")
  1346. }
  1347. if (l.Ltv > 96.5) {
  1348. return errors.New("FHA down payment must be at least 3.5%")
  1349. }
  1350. if (b.Credit < 580 && l.Ltv > 90) {
  1351. return errors.New("FHA down payment must be at least 10%")
  1352. }
  1353. // Debt-to-income must be below 45% if credit score is below 580.
  1354. if (b.Credit < 580 && l.Dti > 45) {
  1355. return errors.New(fmt.Sprintln(
  1356. l.Name, "debt to income is too high for credit score.",
  1357. ))
  1358. }
  1359. return nil
  1360. }
  1361. // Loan option for veterans with no set rules. Mainly placeholder.
  1362. func checkVA(l Loan, b Borrower) error {
  1363. return nil
  1364. }
  1365. // Loan option for residents of rural areas with no set rules.
  1366. // Mainly placeholder.
  1367. func checkUSDA(l Loan, b Borrower) error {
  1368. return nil
  1369. }
  1370. // Should also check loan amount limit maybe with an API.
  1371. func checkEstimate(e Estimate) error {
  1372. if e.Property == "" { return errors.New("Empty property type") }
  1373. if e.Price == 0 { return errors.New("Empty property price") }
  1374. if e.Borrower.Num == 0 {
  1375. return errors.New("Missing number of borrowers")
  1376. }
  1377. if e.Borrower.Credit == 0 {
  1378. return errors.New("Missing borrower credit score")
  1379. }
  1380. if e.Borrower.Income == 0 {
  1381. return errors.New("Missing borrower credit income")
  1382. }
  1383. for _, l := range e.Loans {
  1384. if l.Amount == 0 {
  1385. return errors.New(fmt.Sprintln(l.Name, "loan amount cannot be zero"))
  1386. }
  1387. if l.Term == 0 {
  1388. return errors.New(fmt.Sprintln(l.Name, "loan term cannot be zero"))
  1389. }
  1390. if l.Interest == 0 {
  1391. return errors.New(fmt.Sprintln(l.Name, "loan interest cannot be zero"))
  1392. }
  1393. // Can be used to check rules for specific loan types
  1394. var err error
  1395. switch l.Type.Id {
  1396. case 1:
  1397. err = checkConventional(l, e.Borrower)
  1398. case 2:
  1399. err = checkFHA(l, e.Borrower)
  1400. case 3:
  1401. err = checkVA(l, e.Borrower)
  1402. case 4:
  1403. err = checkUSDA(l, e.Borrower)
  1404. default:
  1405. err = errors.New("Invalid loan type")
  1406. }
  1407. if err != nil { return err }
  1408. }
  1409. return nil
  1410. }
  1411. func validateEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1412. var estimate Estimate
  1413. err := json.NewDecoder(r.Body).Decode(&estimate)
  1414. if err != nil { http.Error(w, err.Error(), 422); return }
  1415. err = checkEstimate(estimate)
  1416. if err != nil { http.Error(w, err.Error(), 406); return }
  1417. }
  1418. func showPDF(w http.ResponseWriter, r *http.Request) {
  1419. // var args []string
  1420. // p := r.URL.Path
  1421. db, err := sql.Open("mysql",
  1422. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
  1423. config["DBUser"],
  1424. config["DBPass"]))
  1425. // w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  1426. err = db.Ping()
  1427. if err != nil {
  1428. fmt.Println("Bad database configuration: %v\n", err)
  1429. panic(err)
  1430. // maybe os.Exit(1) instead
  1431. }
  1432. var pa = template.Must(template.ParseFiles("views/master.tpl",
  1433. "views/test.tpl"))
  1434. // claims, err := getClaims(r)
  1435. if err != nil { w.WriteHeader(500); return }
  1436. users, err := queryUsers(db, 1)
  1437. info := struct {
  1438. Title string
  1439. Name string
  1440. User User
  1441. }{
  1442. Title: "test PDF",
  1443. Name: "idk-random-name",
  1444. User: users[0],
  1445. }
  1446. // fmt.Println(info)
  1447. err = pa.Execute(w, info)
  1448. if err != nil {fmt.Println(err)}
  1449. }
  1450. func clipLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1451. var validTypes []string = []string{"image/png", "image/jpeg"}
  1452. var isValidType bool
  1453. var err error
  1454. // claims, err := getClaims(r)
  1455. if err != nil { http.Error(w, "Invalid token.", 422); return }
  1456. img, t, err := image.Decode(r.Body)
  1457. if err != nil { http.Error(w, "Invalid file.", 422); return }
  1458. for _, v := range validTypes {
  1459. if v == "image/"+t { isValidType = true }
  1460. }
  1461. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  1462. g := gift.New(
  1463. gift.ResizeToFit(400, 200, gift.LanczosResampling),
  1464. )
  1465. dst := image.NewRGBA(g.Bounds(img.Bounds()))
  1466. g.Draw(dst, img)
  1467. w.Header().Set("Content-Type", "image/png")
  1468. err = png.Encode(w, dst)
  1469. if err != nil { http.Error(w, "Error encoding.", 500); return }
  1470. }
  1471. func api(w http.ResponseWriter, r *http.Request) {
  1472. var args []string
  1473. p := r.URL.Path
  1474. db, err := sql.Open("mysql",
  1475. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
  1476. config["DBUser"],
  1477. config["DBPass"]))
  1478. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  1479. err = db.Ping()
  1480. if err != nil {
  1481. fmt.Println("Bad database configuration: %v\n", err)
  1482. panic(err)
  1483. // maybe os.Exit(1) instead
  1484. }
  1485. switch {
  1486. case match(p, "/api/login", &args) &&
  1487. r.Method == http.MethodPost:
  1488. login(w, db, r)
  1489. case match(p, "/api/token", &args) &&
  1490. r.Method == http.MethodGet && guard(r, 1):
  1491. getToken(w, db, r)
  1492. case match(p, "/api/letterhead", &args) &&
  1493. r.Method == http.MethodPost && guard(r, 1):
  1494. clipLetterhead(w, db, r)
  1495. case match(p, "/api/users", &args) && // Array of all users
  1496. r.Method == http.MethodGet && guard(r, 3):
  1497. getUsers(w, db, r)
  1498. case match(p, "/api/user", &args) &&
  1499. r.Method == http.MethodGet && guard(r, 1):
  1500. getUser(w, db, r)
  1501. case match(p, "/api/user", &args) &&
  1502. r.Method == http.MethodPost &&
  1503. guard(r, 3):
  1504. createUser(w, db, r)
  1505. case match(p, "/api/user", &args) &&
  1506. r.Method == http.MethodPatch &&
  1507. guard(r, 3): // For admin to modify any user
  1508. patchUser(w, db, r)
  1509. case match(p, "/api/user", &args) &&
  1510. r.Method == http.MethodPatch &&
  1511. guard(r, 1): // For employees to modify own accounts
  1512. patchSelf(w, db, r)
  1513. case match(p, "/api/user", &args) &&
  1514. r.Method == http.MethodDelete &&
  1515. guard(r, 3):
  1516. deleteUser(w, db, r)
  1517. case match(p, "/api/user/avatar", &args) &&
  1518. r.Method == http.MethodGet &&
  1519. guard(r, 1):
  1520. getAvatar(w, db, r)
  1521. case match(p, "/api/user/avatar", &args) &&
  1522. r.Method == http.MethodPost &&
  1523. guard(r, 1):
  1524. setAvatar(w, db, r)
  1525. case match(p, "/api/user/letterhead", &args) &&
  1526. r.Method == http.MethodGet &&
  1527. guard(r, 1):
  1528. getLetterhead(w, db, r)
  1529. case match(p, "/api/user/letterhead", &args) &&
  1530. r.Method == http.MethodPost &&
  1531. guard(r, 1):
  1532. setLetterhead(w, db, r)
  1533. case match(p, "/api/user/password", &args) &&
  1534. r.Method == http.MethodPost &&
  1535. guard(r, 1):
  1536. changePassword(w, db, r)
  1537. case match(p, "/api/fees", &args) &&
  1538. r.Method == http.MethodGet &&
  1539. guard(r, 1):
  1540. getFeesTemp(w, db, r)
  1541. case match(p, "/api/fee", &args) &&
  1542. r.Method == http.MethodPost &&
  1543. guard(r, 1):
  1544. createFeesTemp(w, db, r)
  1545. case match(p, "/api/fee", &args) &&
  1546. r.Method == http.MethodDelete &&
  1547. guard(r, 1):
  1548. deleteFeeTemp(w, db, r)
  1549. case match(p, "/api/estimates", &args) &&
  1550. r.Method == http.MethodGet &&
  1551. guard(r, 1):
  1552. fetchEstimate(w, db, r)
  1553. case match(p, "/api/estimate", &args) &&
  1554. r.Method == http.MethodPost &&
  1555. guard(r, 1):
  1556. createEstimate(w, db, r)
  1557. case match(p, "/api/estimate/validate", &args) &&
  1558. r.Method == http.MethodPost &&
  1559. guard(r, 1):
  1560. validateEstimate(w, db, r)
  1561. case match(p, "/api/estimate/summarize", &args) &&
  1562. r.Method == http.MethodPost &&
  1563. guard(r, 1):
  1564. summarize(w, db, r)
  1565. default:
  1566. http.Error(w, "Invalid route or token", 404)
  1567. }
  1568. db.Close()
  1569. }
  1570. func route(w http.ResponseWriter, r *http.Request) {
  1571. var page Page
  1572. var args []string
  1573. p := r.URL.Path
  1574. switch {
  1575. case r.Method == "GET" && match(p, "/", &args):
  1576. page = pages[ "home" ]
  1577. case match(p, "/terms", &args):
  1578. page = pages[ "terms" ]
  1579. case match(p, "/app", &args):
  1580. page = pages[ "app" ]
  1581. case match(p, "/test", &args):
  1582. showPDF(w, r)
  1583. return
  1584. default:
  1585. http.NotFound(w, r)
  1586. return
  1587. }
  1588. page.Render(w)
  1589. }
  1590. func main() {
  1591. files := http.FileServer(http.Dir(""))
  1592. http.Handle("/assets/", files)
  1593. http.HandleFunc("/api/", api)
  1594. http.HandleFunc("/", route)
  1595. log.Fatal(http.ListenAndServe(address, nil))
  1596. }