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.
 
 
 
 
 
 

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