Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

2949 Zeilen
62 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. func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1176. var user User
  1177. err := json.NewDecoder(r.Body).Decode(&user)
  1178. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  1179. _, err = mail.ParseAddress(user.Email)
  1180. if err != nil { http.Error(w, "Invalid email.", 422); return }
  1181. if roles[user.Role] == 0 {
  1182. http.Error(w, "Invalid role.", 422)
  1183. }
  1184. user, err = insertUser(db, user)
  1185. if err != nil { http.Error(w, "Error creating user.", 422); return }
  1186. json.NewEncoder(w).Encode(user)
  1187. }
  1188. func checkPassword(db *sql.DB, id int, pass string) bool {
  1189. var p string
  1190. query := `SELECT
  1191. password
  1192. FROM user WHERE user.id = ? AND password = sha2(?, 256)
  1193. `
  1194. row := db.QueryRow(query, id, pass)
  1195. err := row.Scan(&p)
  1196. if err != nil { return false }
  1197. return true
  1198. }
  1199. func setPassword(db *sql.DB, id int, pass string) error {
  1200. query := `UPDATE user
  1201. SET password = sha2(?, 256)
  1202. WHERE user.id = ?
  1203. `
  1204. _, err := db.Exec(query, pass, id)
  1205. if err != nil { return errors.New("Could not insert password.") }
  1206. return nil
  1207. }
  1208. func changePassword(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1209. var pass Password
  1210. claim, err := getClaims(r)
  1211. err = json.NewDecoder(r.Body).Decode(&pass)
  1212. if err != nil { http.Error(w, "Bad fields.", 422); return }
  1213. if checkPassword(db, claim.Id, pass.Old) {
  1214. err = setPassword(db, claim.Id, pass.New)
  1215. } else {
  1216. http.Error(w, "Incorrect old password.", 401)
  1217. return
  1218. }
  1219. if err != nil { http.Error(w, err.Error(), 500); return }
  1220. }
  1221. func fetchAvatar(db *sql.DB, user int) ( []byte, error ) {
  1222. var img []byte
  1223. var query string
  1224. var err error
  1225. query = `SELECT
  1226. avatar
  1227. FROM user WHERE user.id = ?
  1228. `
  1229. row := db.QueryRow(query, user)
  1230. err = row.Scan(&img)
  1231. if err != nil {
  1232. return img, err
  1233. }
  1234. return img, nil
  1235. }
  1236. func insertAvatar(db *sql.DB, user int, img []byte) error {
  1237. query := `UPDATE user
  1238. SET avatar = ?
  1239. WHERE id = ?
  1240. `
  1241. _, err := db.Exec(query, img, user)
  1242. if err != nil {
  1243. return err
  1244. }
  1245. return nil
  1246. }
  1247. func fetchLetterhead(db *sql.DB, user int) ( []byte, error ) {
  1248. var img []byte
  1249. var query string
  1250. var err error
  1251. query = `SELECT
  1252. letterhead
  1253. FROM user WHERE user.id = ?
  1254. `
  1255. row := db.QueryRow(query, user)
  1256. err = row.Scan(&img)
  1257. if err != nil {
  1258. return img, err
  1259. }
  1260. return img, nil
  1261. }
  1262. func insertLetterhead(db *sql.DB, user int, img []byte) error {
  1263. query := `UPDATE user
  1264. SET letterhead = ?
  1265. WHERE id = ?
  1266. `
  1267. _, err := db.Exec(query, img, user)
  1268. if err != nil {
  1269. return err
  1270. }
  1271. return nil
  1272. }
  1273. func setAvatar(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1274. var validTypes []string = []string{"image/png", "image/jpeg"}
  1275. var isValidType bool
  1276. claims, err := getClaims(r)
  1277. if err != nil { http.Error(w, "Invalid token.", 422); return }
  1278. img, err := io.ReadAll(r.Body)
  1279. if err != nil { http.Error(w, "Invalid file.", 422); return }
  1280. for _, v := range validTypes {
  1281. if v == http.DetectContentType(img) { isValidType = true }
  1282. }
  1283. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  1284. err = insertAvatar(db, claims.Id, img)
  1285. if err != nil { http.Error(w, "Could not insert.", 500); return }
  1286. }
  1287. func getAvatar(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1288. claims, err := getClaims(r)
  1289. if err != nil { http.Error(w, "Invalid token.", 422); return }
  1290. img, err := fetchAvatar(db, claims.Id)
  1291. if err != nil { http.Error(w, "Could not retrieve.", 500); return }
  1292. w.Header().Set("Content-Type", http.DetectContentType(img))
  1293. w.Write(img)
  1294. }
  1295. func setLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1296. var validTypes []string = []string{"image/png", "image/jpeg"}
  1297. var isValidType bool
  1298. claims, err := getClaims(r)
  1299. if err != nil { http.Error(w, "Invalid token.", 422); return }
  1300. img, err := io.ReadAll(r.Body)
  1301. if err != nil { http.Error(w, "Invalid file.", 422); return }
  1302. for _, v := range validTypes {
  1303. if v == http.DetectContentType(img) { isValidType = true }
  1304. }
  1305. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  1306. err = insertLetterhead(db, claims.Id, img)
  1307. if err != nil { http.Error(w, "Could not insert.", 500); return }
  1308. }
  1309. func getLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1310. claims, err := getClaims(r)
  1311. if err != nil { http.Error(w, "Invalid token.", 422); return }
  1312. img, err := fetchLetterhead(db, claims.Id)
  1313. if err != nil { http.Error(w, "Could not retrieve.", 500); return }
  1314. w.Header().Set("Content-Type", http.DetectContentType(img))
  1315. w.Write(img)
  1316. }
  1317. func queryBorrower(db *sql.DB, id int) ( Borrower, error ) {
  1318. var borrower Borrower
  1319. var query string
  1320. var err error
  1321. query = `SELECT
  1322. l.id,
  1323. l.credit_score,
  1324. l.num,
  1325. l.monthly_income
  1326. FROM borrower l WHERE l.id = ?
  1327. `
  1328. row := db.QueryRow(query, id)
  1329. err = row.Scan(
  1330. borrower.Id,
  1331. borrower.Credit,
  1332. borrower.Num,
  1333. borrower.Income,
  1334. )
  1335. if err != nil {
  1336. return borrower, err
  1337. }
  1338. return borrower, nil
  1339. }
  1340. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  1341. func getResults(db *sql.DB, e int, id int) ( []Result, error ) {
  1342. var results []Result
  1343. var query string
  1344. var rows *sql.Rows
  1345. var err error
  1346. query = `SELECT
  1347. r.id,
  1348. loan_id,
  1349. loan_payment,
  1350. total_monthly,
  1351. total_fees,
  1352. total_credits,
  1353. cash_to_close
  1354. FROM estimate_result r
  1355. INNER JOIN loan
  1356. ON r.loan_id = loan.id
  1357. WHERE r.id = CASE @e := ? WHEN 0 THEN r.id ELSE @e END
  1358. AND loan.estimate_id = ?
  1359. `
  1360. rows, err = db.Query(query, id, e)
  1361. if err != nil {
  1362. return results, err
  1363. }
  1364. defer rows.Close()
  1365. for rows.Next() {
  1366. var result Result
  1367. if err := rows.Scan(
  1368. &result.Id,
  1369. &result.LoanId,
  1370. &result.LoanPayment,
  1371. &result.TotalMonthly,
  1372. &result.TotalFees,
  1373. &result.TotalCredits,
  1374. &result.CashToClose,
  1375. )
  1376. err != nil {
  1377. return results, err
  1378. }
  1379. results = append(results, result)
  1380. }
  1381. // Prevents runtime panics
  1382. // if len(results) == 0 { return results, errors.New("Result not found.") }
  1383. return results, nil
  1384. }
  1385. // Retrieve an estimate result with a specified loan id
  1386. func getResult(db *sql.DB, loan int) ( Result, error ) {
  1387. var result Result
  1388. var query string
  1389. var err error
  1390. query = `SELECT
  1391. r.id,
  1392. loan_id,
  1393. loan_payment,
  1394. total_monthly,
  1395. total_fees,
  1396. total_credits,
  1397. cash_to_close
  1398. FROM estimate_result r
  1399. INNER JOIN loan
  1400. ON r.loan_id = loan.id
  1401. WHERE loan.Id = ?
  1402. `
  1403. row := db.QueryRow(query, loan)
  1404. err = row.Scan(
  1405. &result.Id,
  1406. &result.LoanId,
  1407. &result.LoanPayment,
  1408. &result.TotalMonthly,
  1409. &result.TotalFees,
  1410. &result.TotalCredits,
  1411. &result.CashToClose,
  1412. )
  1413. if err != nil {
  1414. return result, err
  1415. }
  1416. return result, nil
  1417. }
  1418. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  1419. func getLoans(db *sql.DB, e int, id int) ( []Loan, error ) {
  1420. var loans []Loan
  1421. var query string
  1422. var rows *sql.Rows
  1423. var err error
  1424. query = `SELECT
  1425. l.id,
  1426. l.type_id,
  1427. l.estimate_id,
  1428. l.amount,
  1429. l.term,
  1430. l.interest,
  1431. l.ltv,
  1432. l.dti,
  1433. l.hoi,
  1434. l.tax,
  1435. l.name
  1436. FROM loan l WHERE l.id = CASE @e := ? WHEN 0 THEN l.id ELSE @e END AND
  1437. l.estimate_id = ?
  1438. `
  1439. rows, err = db.Query(query, id, e)
  1440. if err != nil {
  1441. return loans, err
  1442. }
  1443. defer rows.Close()
  1444. for rows.Next() {
  1445. var loan Loan
  1446. if err := rows.Scan(
  1447. &loan.Id,
  1448. &loan.Type.Id,
  1449. &loan.EstimateId,
  1450. &loan.Amount,
  1451. &loan.Term,
  1452. &loan.Interest,
  1453. &loan.Ltv,
  1454. &loan.Dti,
  1455. &loan.Hoi,
  1456. &loan.Tax,
  1457. &loan.Name,
  1458. )
  1459. err != nil {
  1460. return loans, err
  1461. }
  1462. mi, err := getMi(db, loan.Id)
  1463. if err != nil {
  1464. return loans, err
  1465. }
  1466. loan.Mi = mi
  1467. fees, err := getFees(db, loan.Id)
  1468. if err != nil {
  1469. return loans, err
  1470. }
  1471. loan.Fees = fees
  1472. loan.Result, err = getResult(db, loan.Id)
  1473. if err != nil {
  1474. return loans, err
  1475. }
  1476. loan.Type, err = getLoanType(db, loan.Type.Id)
  1477. if err != nil {
  1478. return loans, err
  1479. }
  1480. loans = append(loans, loan)
  1481. }
  1482. // Prevents runtime panics
  1483. if len(loans) == 0 { return loans, errors.New("Loan not found.") }
  1484. return loans, nil
  1485. }
  1486. func getEstimate(db *sql.DB, id int) ( Estimate, error ) {
  1487. estimates, err := getEstimates(db, id, 0)
  1488. if err != nil { return Estimate{}, err }
  1489. return estimates[0], nil
  1490. }
  1491. func getEstimates(db *sql.DB, id int, user int) ( []Estimate, error ) {
  1492. var estimates []Estimate
  1493. var query string
  1494. var rows *sql.Rows
  1495. var err error
  1496. query = `SELECT
  1497. id,
  1498. user_id,
  1499. transaction,
  1500. price,
  1501. property,
  1502. occupancy,
  1503. zip,
  1504. pud
  1505. FROM estimate WHERE id = CASE @e := ? WHEN 0 THEN id ELSE @e END AND
  1506. user_id = CASE @e := ? WHEN 0 THEN user_id ELSE @e END
  1507. `
  1508. rows, err = db.Query(query, id, user)
  1509. if err != nil {
  1510. return estimates, err
  1511. }
  1512. defer rows.Close()
  1513. for rows.Next() {
  1514. var estimate Estimate
  1515. if err := rows.Scan(
  1516. &estimate.Id,
  1517. &estimate.User,
  1518. &estimate.Transaction,
  1519. &estimate.Price,
  1520. &estimate.Property,
  1521. &estimate.Occupancy,
  1522. &estimate.Zip,
  1523. &estimate.Pud,
  1524. )
  1525. err != nil {
  1526. return estimates, err
  1527. }
  1528. err := estimate.getBorrower(db)
  1529. if err != nil {
  1530. return estimates, err
  1531. }
  1532. estimates = append(estimates, estimate)
  1533. }
  1534. // Prevents runtime panics
  1535. if len(estimates) == 0 { return estimates, errors.New("Estimate not found.") }
  1536. for i := range estimates {
  1537. estimates[i].Loans, err = getLoans(db, estimates[i].Id, 0)
  1538. if err != nil { return estimates, err }
  1539. }
  1540. return estimates, nil
  1541. }
  1542. func queryETemplates(db *sql.DB, id int, user int) ( []ETemplate, error ) {
  1543. var eTemplates []ETemplate
  1544. var query string
  1545. var rows *sql.Rows
  1546. var err error
  1547. query = `SELECT
  1548. id,
  1549. estimate_id,
  1550. user_id,
  1551. branch_id
  1552. FROM estimate_template WHERE id = CASE @e := ? WHEN 0 THEN id ELSE @e END AND
  1553. user_id = CASE @e := ? WHEN 0 THEN user_id ELSE @e END
  1554. `
  1555. rows, err = db.Query(query, id, user)
  1556. if err != nil {
  1557. return eTemplates, err
  1558. }
  1559. defer rows.Close()
  1560. for rows.Next() {
  1561. var e ETemplate
  1562. if err := rows.Scan(
  1563. &e.Id,
  1564. &e.Estimate.Id,
  1565. &e.UserId,
  1566. &e.BranchId,
  1567. )
  1568. err != nil {
  1569. return eTemplates, err
  1570. }
  1571. e.Estimate, err = getEstimate(db, e.Estimate.Id)
  1572. if err != nil {
  1573. return eTemplates, err
  1574. }
  1575. eTemplates = append(eTemplates, e)
  1576. }
  1577. return eTemplates, nil
  1578. }
  1579. // Accepts a borrower struct and returns the id of the inserted borrower and
  1580. // any related error.
  1581. func (estimate *Estimate) insertETemplate(db *sql.DB, user int, branch int) error {
  1582. var query string
  1583. var err error
  1584. query = `INSERT INTO estimate_template
  1585. (
  1586. estimate_id,
  1587. user_id,
  1588. branch_id
  1589. )
  1590. VALUES (?, ?, ?)
  1591. `
  1592. _, err = db.Exec(query,
  1593. estimate.Id,
  1594. user,
  1595. branch,
  1596. )
  1597. if err != nil { return err }
  1598. return nil
  1599. }
  1600. // Accepts a borrower struct and returns the id of the inserted borrower and
  1601. // any related error.
  1602. func (estimate *Estimate) insertBorrower(db *sql.DB) error {
  1603. var query string
  1604. var row *sql.Row
  1605. var err error
  1606. query = `INSERT INTO borrower
  1607. (
  1608. estimate_id,
  1609. credit_score,
  1610. monthly_income,
  1611. num
  1612. )
  1613. VALUES (?, ?, ?, ?)
  1614. RETURNING id
  1615. `
  1616. row = db.QueryRow(query,
  1617. estimate.Id,
  1618. estimate.Borrower.Credit,
  1619. estimate.Borrower.Income,
  1620. estimate.Borrower.Num,
  1621. )
  1622. err = row.Scan(&estimate.Borrower.Id)
  1623. if err != nil { return err }
  1624. return nil
  1625. }
  1626. func insertMi(db *sql.DB, mi MI) (int, error) {
  1627. var id int
  1628. query := `INSERT INTO mi
  1629. (
  1630. type,
  1631. label,
  1632. lender,
  1633. rate,
  1634. premium,
  1635. upfront,
  1636. five_year_total,
  1637. initial_premium,
  1638. initial_rate,
  1639. initial_amount
  1640. )
  1641. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1642. RETURNING id`
  1643. row := db.QueryRow(query,
  1644. &mi.Type,
  1645. &mi.Label,
  1646. &mi.Lender,
  1647. &mi.Rate,
  1648. &mi.Premium,
  1649. &mi.Upfront,
  1650. &mi.FiveYearTotal,
  1651. &mi.InitialAllInPremium,
  1652. &mi.InitialAllInRate,
  1653. &mi.InitialAmount,
  1654. )
  1655. err := row.Scan(&id)
  1656. if err != nil { return 0, err }
  1657. return id, nil
  1658. }
  1659. func insertFee(db *sql.DB, fee Fee) (int, error) {
  1660. var id int
  1661. query := `INSERT INTO fee
  1662. (loan_id, amount, perc, type, notes, name, category)
  1663. VALUES (?, ?, ?, ?, ?, ?, ?)
  1664. RETURNING id`
  1665. row := db.QueryRow(query,
  1666. fee.LoanId,
  1667. fee.Amount,
  1668. fee.Perc,
  1669. fee.Type,
  1670. fee.Notes,
  1671. fee.Name,
  1672. fee.Category,
  1673. )
  1674. err := row.Scan(&id)
  1675. if err != nil { return 0, err }
  1676. return id, nil
  1677. }
  1678. func insertLoanType(db *sql.DB, lt LoanType) (int, error) {
  1679. var id int
  1680. query := `INSERT INTO loan_type (branch_id, user_id, name)
  1681. VALUES (NULLIF(?, 0), NULLIF(?, 0), ?)
  1682. RETURNING id`
  1683. row := db.QueryRow(query,
  1684. lt.Branch,
  1685. lt.User,
  1686. lt.Name,
  1687. )
  1688. err := row.Scan(&id)
  1689. if err != nil { return 0, err }
  1690. return id, nil
  1691. }
  1692. func (loan *Loan) insertLoan(db *sql.DB) error {
  1693. var query string
  1694. var row *sql.Row
  1695. var err error
  1696. query = `INSERT INTO loan
  1697. (
  1698. estimate_id,
  1699. type_id,
  1700. amount,
  1701. term,
  1702. interest,
  1703. ltv,
  1704. dti,
  1705. hoi,
  1706. tax,
  1707. name
  1708. )
  1709. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1710. RETURNING id
  1711. `
  1712. row = db.QueryRow(query,
  1713. loan.EstimateId,
  1714. loan.Type.Id,
  1715. loan.Amount,
  1716. loan.Term,
  1717. loan.Interest,
  1718. loan.Ltv,
  1719. loan.Dti,
  1720. loan.Hoi,
  1721. loan.Tax,
  1722. loan.Name,
  1723. )
  1724. err = row.Scan(&loan.Id)
  1725. if err != nil { return err }
  1726. _, err = insertMi(db, loan.Mi)
  1727. if err != nil { return err }
  1728. for i := range loan.Fees {
  1729. loan.Fees[i].LoanId = loan.Id
  1730. _, err := insertFee(db, loan.Fees[i])
  1731. if err != nil { return err }
  1732. }
  1733. return nil
  1734. }
  1735. func (estimate *Estimate) insertEstimate(db *sql.DB) (error){
  1736. var query string
  1737. var row *sql.Row
  1738. var err error
  1739. // var id int // Inserted estimate's id
  1740. query = `INSERT INTO estimate
  1741. (
  1742. user_id,
  1743. transaction,
  1744. price,
  1745. property,
  1746. occupancy,
  1747. zip,
  1748. pud
  1749. )
  1750. VALUES (?, ?, ?, ?, ?, ?, ?)
  1751. RETURNING id
  1752. `
  1753. row = db.QueryRow(query,
  1754. estimate.User,
  1755. estimate.Transaction,
  1756. estimate.Price,
  1757. estimate.Property,
  1758. estimate.Occupancy,
  1759. estimate.Zip,
  1760. estimate.Pud,
  1761. )
  1762. err = row.Scan(&estimate.Id)
  1763. if err != nil { return err }
  1764. err = estimate.insertBorrower(db)
  1765. if err != nil { return err }
  1766. for i := range estimate.Loans {
  1767. estimate.Loans[i].EstimateId = estimate.Id
  1768. err = estimate.Loans[i].insertLoan(db)
  1769. if err != nil { return err }
  1770. }
  1771. return nil
  1772. }
  1773. func (estimate *Estimate) del(db *sql.DB, user int) (error) {
  1774. var query string
  1775. var err error
  1776. query = `DELETE FROM estimate WHERE id = ? AND user_id = ?`
  1777. _, err = db.Exec(query, estimate.Id, user)
  1778. if err != nil { return err }
  1779. return nil
  1780. }
  1781. func (et *ETemplate) del(db *sql.DB, user int) (error) {
  1782. var query string
  1783. var err error
  1784. query = `DELETE FROM estimate_template WHERE id = ? AND user_id = ?`
  1785. _, err = db.Exec(query, et.Id, user)
  1786. if err != nil { return err }
  1787. return nil
  1788. }
  1789. func (eTemplate *ETemplate) insert(db *sql.DB) (error) {
  1790. var query string
  1791. var row *sql.Row
  1792. var err error
  1793. query = `INSERT INTO estimate_template
  1794. (
  1795. user_id,
  1796. branch_id,
  1797. estimate_id,
  1798. )
  1799. VALUES (?, ?, ?)
  1800. RETURNING id
  1801. `
  1802. row = db.QueryRow(query,
  1803. eTemplate.UserId,
  1804. eTemplate.BranchId,
  1805. eTemplate.Estimate.Id,
  1806. )
  1807. err = row.Scan(&eTemplate.Id)
  1808. if err != nil { return err }
  1809. return nil
  1810. }
  1811. func createEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1812. var estimate Estimate
  1813. err := json.NewDecoder(r.Body).Decode(&estimate)
  1814. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  1815. claims, err := getClaims(r)
  1816. estimate.User = claims.Id
  1817. err = estimate.insertEstimate(db)
  1818. if err != nil { http.Error(w, err.Error(), 422); return }
  1819. estimate.makeResults()
  1820. err = estimate.insertResults(db)
  1821. if err != nil { http.Error(w, err.Error(), 500); return }
  1822. e, err := getEstimates(db, estimate.Id, 0)
  1823. if err != nil { http.Error(w, err.Error(), 500); return }
  1824. json.NewEncoder(w).Encode(e[0])
  1825. }
  1826. // Query all estimates that belong to the current user
  1827. func fetchEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1828. var estimates []Estimate
  1829. claims, err := getClaims(r)
  1830. estimates, err = getEstimates(db, 0, claims.Id)
  1831. if err != nil { http.Error(w, err.Error(), 500); return }
  1832. json.NewEncoder(w).Encode(estimates)
  1833. }
  1834. func checkConventional(l Loan, b Borrower) error {
  1835. if b.Credit < 620 {
  1836. return errors.New("Credit score too low for conventional loan")
  1837. }
  1838. // Buyer needs to put down 20% to avoid mortgage insurance
  1839. if (l.Ltv > 80 && l.Mi.Rate == 0) {
  1840. return errors.New(fmt.Sprintln(
  1841. l.Name,
  1842. "down payment must be 20% to avoid insurance",
  1843. ))
  1844. }
  1845. return nil
  1846. }
  1847. func checkFHA(l Loan, b Borrower) error {
  1848. if b.Credit < 500 {
  1849. return errors.New("Credit score too low for FHA loan")
  1850. }
  1851. if (l.Ltv > 96.5) {
  1852. return errors.New("FHA down payment must be at least 3.5%")
  1853. }
  1854. if (b.Credit < 580 && l.Ltv > 90) {
  1855. return errors.New("FHA down payment must be at least 10%")
  1856. }
  1857. // Debt-to-income must be below 45% if credit score is below 580.
  1858. if (b.Credit < 580 && l.Dti > 45) {
  1859. return errors.New(fmt.Sprintln(
  1860. l.Name, "debt to income is too high for credit score.",
  1861. ))
  1862. }
  1863. return nil
  1864. }
  1865. // Loan option for veterans with no set rules. Mainly placeholder.
  1866. func checkVA(l Loan, b Borrower) error {
  1867. return nil
  1868. }
  1869. // Loan option for residents of rural areas with no set rules.
  1870. // Mainly placeholder.
  1871. func checkUSDA(l Loan, b Borrower) error {
  1872. return nil
  1873. }
  1874. // Should also check loan amount limit maybe with an API.
  1875. func checkEstimate(e Estimate) error {
  1876. if e.Property == "" { return errors.New("Empty property type") }
  1877. if e.Price == 0 { return errors.New("Empty property price") }
  1878. if e.Borrower.Num == 0 {
  1879. return errors.New("Missing number of borrowers")
  1880. }
  1881. if e.Borrower.Credit == 0 {
  1882. return errors.New("Missing borrower credit score")
  1883. }
  1884. if e.Borrower.Income == 0 {
  1885. return errors.New("Missing borrower credit income")
  1886. }
  1887. for _, l := range e.Loans {
  1888. if l.Amount == 0 {
  1889. return errors.New(fmt.Sprintln(l.Name, "loan amount cannot be zero"))
  1890. }
  1891. if l.Term == 0 {
  1892. return errors.New(fmt.Sprintln(l.Name, "loan term cannot be zero"))
  1893. }
  1894. if l.Interest == 0 {
  1895. return errors.New(fmt.Sprintln(l.Name, "loan interest cannot be zero"))
  1896. }
  1897. // Can be used to check rules for specific loan types
  1898. var err error
  1899. switch l.Type.Id {
  1900. case 1:
  1901. err = checkConventional(l, e.Borrower)
  1902. case 2:
  1903. err = checkFHA(l, e.Borrower)
  1904. case 3:
  1905. err = checkVA(l, e.Borrower)
  1906. case 4:
  1907. err = checkUSDA(l, e.Borrower)
  1908. default:
  1909. err = errors.New("Invalid loan type")
  1910. }
  1911. if err != nil { return err }
  1912. }
  1913. return nil
  1914. }
  1915. func validateEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1916. var estimate Estimate
  1917. err := json.NewDecoder(r.Body).Decode(&estimate)
  1918. if err != nil { http.Error(w, err.Error(), 422); return }
  1919. err = checkEstimate(estimate)
  1920. if err != nil { http.Error(w, err.Error(), 406); return }
  1921. }
  1922. func checkPdf(w http.ResponseWriter, r *http.Request) {
  1923. db, err := sql.Open("mysql",
  1924. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter_dev",
  1925. os.Getenv("DBUser"),
  1926. os.Getenv("DBPass")))
  1927. // w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  1928. err = db.Ping()
  1929. if err != nil {
  1930. fmt.Println("Bad database configuration: %v\n", err)
  1931. panic(err)
  1932. // maybe os.Exit(1) instead
  1933. }
  1934. estimates, err := getEstimates(db, 1, 0)
  1935. if err != nil { w.WriteHeader(500); return }
  1936. // claims, err := getClaims(r)
  1937. if err != nil { w.WriteHeader(500); return }
  1938. user, err := queryUser(db, 1)
  1939. info := Report{
  1940. Title: "test PDF",
  1941. Name: "idk-random-name",
  1942. User: user,
  1943. Estimate: estimates[0],
  1944. }
  1945. avatar, err := fetchAvatar(db, info.User.Id)
  1946. letterhead, err := fetchLetterhead(db, info.User.Id)
  1947. info.Avatar =
  1948. base64.StdEncoding.EncodeToString(avatar)
  1949. info.Letterhead =
  1950. base64.StdEncoding.EncodeToString(letterhead)
  1951. for l := range info.Estimate.Loans {
  1952. loan := info.Estimate.Loans[l]
  1953. for f := range info.Estimate.Loans[l].Fees {
  1954. if info.Estimate.Loans[l].Fees[f].Amount < 0 {
  1955. loan.Credits = append(loan.Credits, loan.Fees[f])
  1956. }
  1957. }
  1958. }
  1959. err = pages["report"].tpl.ExecuteTemplate(w, "master.tpl", info)
  1960. if err != nil {fmt.Println(err)}
  1961. }
  1962. func getETemplates(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1963. claims, err := getClaims(r)
  1964. et, err := queryETemplates(db, 0, claims.Id)
  1965. if err != nil { http.Error(w, err.Error(), 500); return }
  1966. json.NewEncoder(w).Encode(et)
  1967. }
  1968. func createETemplate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1969. var estimate Estimate
  1970. err := json.NewDecoder(r.Body).Decode(&estimate)
  1971. if err != nil { http.Error(w, err.Error(), 422); return }
  1972. claims, err := getClaims(r)
  1973. if err != nil { http.Error(w, err.Error(), 422); return }
  1974. err = estimate.insertETemplate(db, claims.Id, 0)
  1975. if err != nil { http.Error(w, err.Error(), 500); return }
  1976. }
  1977. func getPdf(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1978. var estimate Estimate
  1979. err := json.NewDecoder(r.Body).Decode(&estimate)
  1980. cmd := exec.Command("wkhtmltopdf", "-", "-")
  1981. stdout, err := cmd.StdoutPipe()
  1982. if err != nil {
  1983. w.WriteHeader(500);
  1984. log.Println(err)
  1985. return
  1986. }
  1987. stdin, err := cmd.StdinPipe()
  1988. if err != nil {
  1989. w.WriteHeader(500);
  1990. log.Println(err)
  1991. return
  1992. }
  1993. if err := cmd.Start(); err != nil {
  1994. log.Fatal(err)
  1995. }
  1996. claims, err := getClaims(r)
  1997. if err != nil {
  1998. w.WriteHeader(500);
  1999. log.Println(err)
  2000. return
  2001. }
  2002. user, err := queryUser(db, claims.Id)
  2003. info := Report{
  2004. Title: "test PDF",
  2005. Name: "idk-random-name",
  2006. User: user,
  2007. Estimate: estimate,
  2008. }
  2009. avatar, err := fetchAvatar(db, info.User.Id)
  2010. letterhead, err := fetchLetterhead(db, info.User.Id)
  2011. if len(avatar) > 1 {
  2012. info.Avatar =
  2013. base64.StdEncoding.EncodeToString(avatar)
  2014. }
  2015. if len(letterhead) > 1 {
  2016. info.Letterhead =
  2017. base64.StdEncoding.EncodeToString(letterhead)
  2018. }
  2019. err = pages["report"].tpl.ExecuteTemplate(stdin, "master.tpl", info)
  2020. if err != nil {
  2021. w.WriteHeader(500);
  2022. log.Println(err)
  2023. return
  2024. }
  2025. stdin.Close()
  2026. buf, err := io.ReadAll(stdout)
  2027. if _, err := w.Write(buf); err != nil {
  2028. w.WriteHeader(500);
  2029. log.Println(err)
  2030. return
  2031. }
  2032. if err := cmd.Wait(); err != nil {
  2033. log.Println(err)
  2034. return
  2035. }
  2036. }
  2037. func clipLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  2038. var validTypes []string = []string{"image/png", "image/jpeg"}
  2039. var isValidType bool
  2040. var err error
  2041. // claims, err := getClaims(r)
  2042. if err != nil { http.Error(w, "Invalid token.", 422); return }
  2043. img, t, err := image.Decode(r.Body)
  2044. if err != nil {
  2045. http.Error(w, "Invalid file, JPEG and PNG only.", 422)
  2046. return
  2047. }
  2048. for _, v := range validTypes {
  2049. if v == "image/"+t { isValidType = true }
  2050. }
  2051. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  2052. g := gift.New(
  2053. gift.ResizeToFit(400, 200, gift.LanczosResampling),
  2054. )
  2055. dst := image.NewRGBA(g.Bounds(img.Bounds()))
  2056. g.Draw(dst, img)
  2057. w.Header().Set("Content-Type", "image/png")
  2058. err = png.Encode(w, dst)
  2059. if err != nil { http.Error(w, "Error encoding.", 500); return }
  2060. }
  2061. func api(w http.ResponseWriter, r *http.Request) {
  2062. var args []string
  2063. p := r.URL.Path
  2064. db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
  2065. os.Getenv("DBUser"),
  2066. os.Getenv("DBPass"),
  2067. os.Getenv("DBName"),
  2068. ))
  2069. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  2070. err = db.Ping()
  2071. if err != nil {
  2072. fmt.Println("Bad database configuration: %v\n", err)
  2073. panic(err)
  2074. // maybe os.Exit(1) instead
  2075. }
  2076. switch {
  2077. case match(p, "/api/login", &args) &&
  2078. r.Method == http.MethodPost:
  2079. login(w, db, r)
  2080. case match(p, "/api/token", &args) &&
  2081. r.Method == http.MethodGet && guard(r, 1):
  2082. getToken(w, db, r)
  2083. case match(p, "/api/letterhead", &args) &&
  2084. r.Method == http.MethodPost && guard(r, 1):
  2085. clipLetterhead(w, db, r)
  2086. case match(p, "/api/users", &args) && // Array of all users
  2087. r.Method == http.MethodGet && guard(r, 3):
  2088. getUsers(w, db, r)
  2089. case match(p, "/api/user", &args) &&
  2090. r.Method == http.MethodGet && guard(r, 1):
  2091. getUser(w, db, r)
  2092. case match(p, "/api/user", &args) &&
  2093. r.Method == http.MethodPost &&
  2094. guard(r, 3):
  2095. createUser(w, db, r)
  2096. case match(p, "/api/user", &args) &&
  2097. r.Method == http.MethodPatch &&
  2098. guard(r, 3): // For admin to modify any user
  2099. patchUser(w, db, r)
  2100. case match(p, "/api/user", &args) &&
  2101. r.Method == http.MethodPatch &&
  2102. guard(r, 1): // For employees to modify own accounts
  2103. patchSelf(w, db, r)
  2104. case match(p, "/api/user", &args) &&
  2105. r.Method == http.MethodDelete &&
  2106. guard(r, 3):
  2107. deleteUser(w, db, r)
  2108. case match(p, "/api/user/avatar", &args) &&
  2109. r.Method == http.MethodGet &&
  2110. guard(r, 1):
  2111. getAvatar(w, db, r)
  2112. case match(p, "/api/user/avatar", &args) &&
  2113. r.Method == http.MethodPost &&
  2114. guard(r, 1):
  2115. setAvatar(w, db, r)
  2116. case match(p, "/api/user/letterhead", &args) &&
  2117. r.Method == http.MethodGet &&
  2118. guard(r, 1):
  2119. getLetterhead(w, db, r)
  2120. case match(p, "/api/user/letterhead", &args) &&
  2121. r.Method == http.MethodPost &&
  2122. guard(r, 1):
  2123. setLetterhead(w, db, r)
  2124. case match(p, "/api/user/password", &args) &&
  2125. r.Method == http.MethodPost &&
  2126. guard(r, 1):
  2127. changePassword(w, db, r)
  2128. case match(p, "/api/fees", &args) &&
  2129. r.Method == http.MethodGet &&
  2130. guard(r, 1):
  2131. getFeesTemp(w, db, r)
  2132. case match(p, "/api/fee", &args) &&
  2133. r.Method == http.MethodPost &&
  2134. guard(r, 1):
  2135. createFeesTemp(w, db, r)
  2136. case match(p, "/api/fee", &args) &&
  2137. r.Method == http.MethodDelete &&
  2138. guard(r, 1):
  2139. deleteFeeTemp(w, db, r)
  2140. case match(p, "/api/estimates", &args) &&
  2141. r.Method == http.MethodGet &&
  2142. guard(r, 1):
  2143. fetchEstimate(w, db, r)
  2144. case match(p, "/api/estimate", &args) &&
  2145. r.Method == http.MethodPost &&
  2146. guard(r, 1):
  2147. createEstimate(w, db, r)
  2148. case match(p, "/api/estimate", &args) &&
  2149. r.Method == http.MethodDelete &&
  2150. guard(r, 1):
  2151. deleteEstimate(w, db, r)
  2152. case match(p, "/api/estimate/validate", &args) &&
  2153. r.Method == http.MethodPost &&
  2154. guard(r, 1):
  2155. validateEstimate(w, db, r)
  2156. case match(p, "/api/estimate/summarize", &args) &&
  2157. r.Method == http.MethodPost &&
  2158. guard(r, 1):
  2159. summarize(w, db, r)
  2160. case match(p, "/api/templates", &args) &&
  2161. r.Method == http.MethodGet &&
  2162. guard(r, 1):
  2163. getETemplates(w, db, r)
  2164. case match(p, "/api/templates", &args) &&
  2165. r.Method == http.MethodPost &&
  2166. guard(r, 1):
  2167. createETemplate(w, db, r)
  2168. case match(p, "/api/templates", &args) &&
  2169. r.Method == http.MethodDelete &&
  2170. guard(r, 1):
  2171. deleteET(w, db, r)
  2172. case match(p, "/api/pdf", &args) &&
  2173. r.Method == http.MethodPost &&
  2174. guard(r, 1):
  2175. getPdf(w, db, r)
  2176. default:
  2177. http.Error(w, "Invalid route or token", 404)
  2178. }
  2179. db.Close()
  2180. }
  2181. func route(w http.ResponseWriter, r *http.Request) {
  2182. var page Page
  2183. var args []string
  2184. p := r.URL.Path
  2185. switch {
  2186. case r.Method == "GET" && match(p, "/", &args):
  2187. page = pages[ "home" ]
  2188. case match(p, "/terms", &args):
  2189. page = pages[ "terms" ]
  2190. case match(p, "/app", &args):
  2191. page = pages[ "app" ]
  2192. default:
  2193. http.NotFound(w, r)
  2194. return
  2195. }
  2196. page.Render(w)
  2197. }
  2198. func serve() {
  2199. files := http.FileServer(http.Dir(""))
  2200. http.Handle("/assets/", files)
  2201. http.HandleFunc("/api/", api)
  2202. http.HandleFunc("/", route)
  2203. log.Fatal(http.ListenAndServe(address, nil))
  2204. }
  2205. func dbReset(db *sql.DB) {
  2206. b, err := os.ReadFile("migrations/reset.sql")
  2207. if err != nil {
  2208. log.Fatal(err)
  2209. }
  2210. _, err = db.Exec(string(b))
  2211. if err != nil {
  2212. log.Fatal(err)
  2213. }
  2214. b, err = os.ReadFile("migrations/0_29092022_setup_tables.sql")
  2215. if err != nil {
  2216. log.Fatal(err)
  2217. }
  2218. _, err = db.Exec(string(b))
  2219. if err != nil {
  2220. log.Fatal(err)
  2221. }
  2222. }
  2223. func generateFees(loan Loan) []Fee {
  2224. var fees []Fee
  2225. var fee Fee
  2226. p := gofakeit.Float32Range(0.5, 10)
  2227. size := gofakeit.Number(1, 10)
  2228. for f := 0; f < size; f++ {
  2229. fee = Fee{
  2230. Amount: int(float32(loan.Amount)*p/100),
  2231. Perc: p,
  2232. Name: gofakeit.BuzzWord(),
  2233. Type: feeTypes[gofakeit.Number(0, len(feeTypes) - 1)],
  2234. }
  2235. fees = append(fees, fee)
  2236. }
  2237. return fees
  2238. }
  2239. func generateCredits(loan Loan) []Fee {
  2240. var fees []Fee
  2241. var fee Fee
  2242. p := gofakeit.Float32Range(-10, -0.5)
  2243. size := gofakeit.Number(1, 10)
  2244. for f := 0; f < size; f++ {
  2245. fee = Fee{
  2246. Amount: int(float32(loan.Amount)*p/100),
  2247. Perc: p,
  2248. Name: gofakeit.BuzzWord(),
  2249. Type: feeTypes[gofakeit.Number(0, len(feeTypes) - 1)],
  2250. }
  2251. fees = append(fees, fee)
  2252. }
  2253. return fees
  2254. }
  2255. func seedAddresses(db *sql.DB) []Address {
  2256. addresses := make([]Address, 10)
  2257. for i, a := range addresses {
  2258. a.Street = gofakeit.Street()
  2259. a.City = gofakeit.City()
  2260. a.Region = gofakeit.State()
  2261. a.Country = "Canada"
  2262. a.Full = fmt.Sprintf("%s, %s %s", a.Street, a.City, a.Region)
  2263. id, err := insertAddress(db, a)
  2264. if err != nil {log.Println(err); break}
  2265. addresses[i].Id = id
  2266. }
  2267. return addresses
  2268. }
  2269. func seedBranches(db *sql.DB, addresses []Address) []Branch {
  2270. branches := make([]Branch, 4)
  2271. for i := range branches {
  2272. branches[i].Name = gofakeit.Company()
  2273. branches[i].Type = "NMLS"
  2274. branches[i].Letterhead = gofakeit.ImagePng(400, 200)
  2275. branches[i].Num = gofakeit.HexUint8()
  2276. branches[i].Phone = gofakeit.Phone()
  2277. branches[i].Address.Id = gofakeit.Number(1, 5)
  2278. id, err := insertBranch(db, branches[i])
  2279. if err != nil {log.Println(err); break}
  2280. branches[i].Id = id
  2281. }
  2282. return branches
  2283. }
  2284. func seedUsers(db *sql.DB, addresses []Address, branches []Branch) []User {
  2285. users := make([]User, 10)
  2286. for i := range users {
  2287. p := gofakeit.Person()
  2288. users[i].FirstName = p.FirstName
  2289. users[i].LastName = p.LastName
  2290. users[i].Email = p.Contact.Email
  2291. users[i].Phone = p.Contact.Phone
  2292. users[i].Branch = branches[gofakeit.Number(0, 3)]
  2293. users[i].Address = addresses[gofakeit.Number(1, 9)]
  2294. // users[i].Letterhead = gofakeit.ImagePng(400, 200)
  2295. // users[i].Avatar = gofakeit.ImagePng(200, 200)
  2296. users[i].Country = []string{"Canada", "USA"}[gofakeit.Number(0, 1)]
  2297. users[i].Password = "test123"
  2298. users[i].Verified = true
  2299. users[i].Title = "Loan Officer"
  2300. users[i].Status = "Subscribed"
  2301. users[i].Role = "User"
  2302. }
  2303. users[0].Email = "test@example.com"
  2304. users[0].Email = "test@example.com"
  2305. users[1].Email = "test2@example.com"
  2306. users[1].Status = "Branch"
  2307. users[1].Role = "Manager"
  2308. users[2].Email = "test3@example.com"
  2309. users[2].Status = "Free"
  2310. users[2].Role = "Admin"
  2311. for i := range users {
  2312. u, err := insertUser(db, users[i])
  2313. if err != nil {log.Println(err); break}
  2314. users[i].Id = u.Id
  2315. }
  2316. return users
  2317. }
  2318. func seedLicenses(db *sql.DB, users []User) []License {
  2319. licenses := make([]License, len(users))
  2320. for i := range licenses {
  2321. licenses[i].UserId = users[i].Id
  2322. licenses[i].Type = []string{"NMLS", "FSRA"}[gofakeit.Number(0, 1)]
  2323. licenses[i].Num = gofakeit.UUID()
  2324. id, err := insertLicense(db, licenses[i])
  2325. if err != nil {log.Println(err); break}
  2326. licenses[i].Id = id
  2327. }
  2328. return licenses
  2329. }
  2330. func seedLoanTypes(db *sql.DB) []LoanType {
  2331. var loantypes []LoanType
  2332. var loantype LoanType
  2333. var err error
  2334. loantype = LoanType{Branch: 0, User: 0, Name: "Conventional"}
  2335. loantype.Id, err = insertLoanType(db, loantype)
  2336. if err != nil { panic(err) }
  2337. loantypes = append(loantypes, loantype)
  2338. loantype = LoanType{Branch: 0, User: 0, Name: "FHA"}
  2339. loantype.Id, err = insertLoanType(db, loantype)
  2340. if err != nil { panic(err) }
  2341. loantypes = append(loantypes, loantype)
  2342. loantype = LoanType{Branch: 0, User: 0, Name: "USDA"}
  2343. loantype.Id, err = insertLoanType(db, loantype)
  2344. if err != nil { panic(err) }
  2345. loantypes = append(loantypes, loantype)
  2346. loantype = LoanType{Branch: 0, User: 0, Name: "VA"}
  2347. loantype.Id, err = insertLoanType(db, loantype)
  2348. if err != nil { panic(err) }
  2349. loantypes = append(loantypes, loantype)
  2350. return loantypes
  2351. }
  2352. func seedEstimates(db *sql.DB, users []User, ltypes []LoanType) []Estimate {
  2353. var estimates []Estimate
  2354. var estimate Estimate
  2355. var l Loan
  2356. var err error
  2357. for i := 0; i < 15; i++ {
  2358. estimate = Estimate{}
  2359. estimate.User = users[gofakeit.Number(0, len(users) - 1)].Id
  2360. estimate.Borrower = Borrower{
  2361. Credit: gofakeit.Number(600, 800),
  2362. Income: gofakeit.Number(1000000, 15000000),
  2363. Num: gofakeit.Number(1, 20),
  2364. }
  2365. estimate.Transaction = []string{"Purchase", "Refinance"}[gofakeit.Number(0, 1)]
  2366. estimate.Price = gofakeit.Number(50000, 200000000)
  2367. estimate.Property =
  2368. propertyTypes[gofakeit.Number(0, len(propertyTypes) - 1)]
  2369. estimate.Occupancy =
  2370. []string{"Primary", "Secondary", "Investment"}[gofakeit.Number(0, 2)]
  2371. estimate.Zip = gofakeit.Zip()
  2372. lsize := gofakeit.Number(1, 6)
  2373. for j := 0; j < lsize; j++ {
  2374. l.Type = ltypes[gofakeit.Number(0, len(ltypes) - 1)]
  2375. l.Amount = gofakeit.Number(
  2376. int(float32(estimate.Price)*0.5),
  2377. int(float32(estimate.Price)*0.93))
  2378. l.Term = gofakeit.Number(4, 30)
  2379. l.Hoi = gofakeit.Number(50000, 700000)
  2380. l.Hazard = gofakeit.Number(5000, 200000)
  2381. l.Tax = gofakeit.Number(5000, 200000)
  2382. l.Interest = gofakeit.Float32Range(0.5, 8)
  2383. l.Fees = generateFees(l)
  2384. l.Credits = generateCredits(l)
  2385. l.Name = gofakeit.AdjectiveDescriptive()
  2386. estimate.Loans = append(estimate.Loans, l)
  2387. }
  2388. estimates = append(estimates, estimate)
  2389. }
  2390. estimates[0].User = users[0].Id
  2391. estimates[1].User = users[0].Id
  2392. for i := range estimates {
  2393. err = estimates[i].insertEstimate(db)
  2394. if err != nil {log.Println(err); return estimates}
  2395. }
  2396. return estimates
  2397. }
  2398. func seedResults(db *sql.DB, estimates []Estimate) error {
  2399. var err error
  2400. for i := range estimates {
  2401. estimates[i].makeResults()
  2402. err = estimates[i].insertResults(db)
  2403. if err != nil {log.Println(err); return err}
  2404. }
  2405. return nil
  2406. }
  2407. func dbSeed(db *sql.DB) {
  2408. addresses := seedAddresses(db)
  2409. branches := seedBranches(db, addresses)
  2410. users := seedUsers(db, addresses, branches)
  2411. _ = seedLicenses(db, users)
  2412. loantypes := seedLoanTypes(db)
  2413. estimates := seedEstimates(db, users, loantypes)
  2414. _ = seedResults(db, estimates)
  2415. }
  2416. func dev(args []string) {
  2417. os.Setenv("DBName", "skouter_dev")
  2418. os.Setenv("DBUser", "tester")
  2419. os.Setenv("DBPass", "test123")
  2420. db, err := sql.Open("mysql",
  2421. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s?multiStatements=true",
  2422. os.Getenv("DBUser"),
  2423. os.Getenv("DBPass"),
  2424. os.Getenv("DBName"),
  2425. ))
  2426. err = db.Ping()
  2427. if err != nil {
  2428. log.Println("Bad database configuration: %v", err)
  2429. panic(err)
  2430. // maybe os.Exit(1) instead
  2431. }
  2432. if len(args) == 0 {
  2433. serve()
  2434. return
  2435. }
  2436. switch args[0] {
  2437. case "seed":
  2438. dbSeed(db)
  2439. case "reset":
  2440. dbReset(db)
  2441. default:
  2442. return
  2443. }
  2444. db.Close()
  2445. }
  2446. func check(args []string) {
  2447. os.Setenv("DBName", "skouter_dev")
  2448. os.Setenv("DBUser", "tester")
  2449. os.Setenv("DBPass", "test123")
  2450. files := http.FileServer(http.Dir(""))
  2451. http.Handle("/assets/", files)
  2452. http.HandleFunc("/", checkPdf)
  2453. log.Fatal(http.ListenAndServe(address, nil))
  2454. }
  2455. func main() {
  2456. if len(os.Args) <= 1 {
  2457. serve()
  2458. return
  2459. }
  2460. switch os.Args[1] {
  2461. case "dev":
  2462. dev(os.Args[2:])
  2463. case "checkpdf":
  2464. check(os.Args[2:])
  2465. default:
  2466. return
  2467. }
  2468. }