Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

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