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

1605 lines
34 KiB

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