Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
 
 
 
 
 
 

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