Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 
 
 

1528 líneas
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. }