Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

2975 рядки
63 KiB

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