Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

1075 linhas
24 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. // pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
  19. "github.com/golang-jwt/jwt/v4"
  20. )
  21. type User struct {
  22. Id int `json:"id"`
  23. Email string `json:"email"`
  24. FirstName string `json:"firstName"`
  25. LastName string `json:"lastName"`
  26. BranchId int `json:"branchId"`
  27. Status string `json:"status"`
  28. Country string `json:"country"`
  29. Title string `json:"title"`
  30. Verified bool `json:"verified"`
  31. Role string `json:"role"`
  32. Password string `json:"password,omitempty"`
  33. }
  34. type UserClaims struct {
  35. Id int `json:"id"`
  36. Role string `json:"role"`
  37. Exp string `json:"exp"`
  38. }
  39. type Page struct {
  40. tpl *template.Template
  41. Title string
  42. Name string
  43. }
  44. type Borrower struct {
  45. Id int `json:"id"`
  46. Credit int `json:"credit"`
  47. Income int `json:"income"`
  48. Num int `json:"num"`
  49. }
  50. type FeeTemplate struct {
  51. Id int `json:"id"`
  52. User int `json:"user"`
  53. Branch int `json:"branch"`
  54. Amount int `json:"amount"`
  55. Perc int `json:"perc"`
  56. Type string `json:"type"`
  57. Notes string `json:"notes"`
  58. Name string `json:"name"`
  59. Category string `json:"category"`
  60. Auto bool `json:"auto"`
  61. }
  62. type Fee struct {
  63. Id int `json:"id"`
  64. LoanId int `json:"loan_id"`
  65. Amount int `json:"amount"`
  66. Perc int `json:"perc"`
  67. Type string `json:"type"`
  68. Notes string `json:"notes"`
  69. Name string `json:"name"`
  70. Category string `json:"category"`
  71. }
  72. type LoanType struct {
  73. Id int `json:"id"`
  74. User int `json:"user"`
  75. Branch int `json:"branch"`
  76. Name string `json:"name"`
  77. }
  78. type Loan struct {
  79. Id int `json:id`
  80. EstimateId int `json:estimate_id`
  81. Type LoanType `json:"loanType"`
  82. Amount int `json:"loanAmount"`
  83. Amortization string `json:"loanAmount"`
  84. Term int `json:"term"`
  85. Ltv float32 `json:"ltv"`
  86. Dti float32 `json:"dti"`
  87. Hoi int `json:"hoi"`
  88. Interest int `json:"interest"`
  89. Mi MI `json:"mi"`
  90. Fees []Fee `json:"fees"`
  91. Name string `json:"name"`
  92. }
  93. type MI struct {
  94. Type string `json:"user"`
  95. Label string `json:"label"`
  96. Lender string `json:"lender"`
  97. Rate float32 `json:"rate"`
  98. Premium float32 `json:"premium"`
  99. Upfront float32 `json:"upfront"`
  100. Monthly bool `json:"monthly"`
  101. FiveYearTotal float32 `json:"fiveYearTotal"`
  102. InitialAllInPremium float32 `json:"initialAllInPremium"`
  103. InitialAllInRate float32 `json:"initialAllInRate"`
  104. InitialAmount float32 `json:"initialAmount"`
  105. }
  106. type Result struct {
  107. User int `json:"user"`
  108. Borrower Borrower `json:"borrower"`
  109. Transaction string `json:"transaction"`
  110. Price int `json:"price"`
  111. Property string `json:"property"`
  112. Occupancy string `json:"occupancy"`
  113. Zip string `json:"zip"`
  114. Pud bool `json:"pud"`
  115. Loans []Loan `json:"loans"`
  116. }
  117. type Estimate struct {
  118. Id int `json:"id"`
  119. User int `json:"user"`
  120. Borrower Borrower `json:"borrower"`
  121. Transaction int `json:"transaction"`
  122. Price int `json:"price"`
  123. Property string `json:"property"`
  124. Occupancy string `json:"occupancy"`
  125. Zip string `json:"zip"`
  126. Pud bool `json:"pud"`
  127. Loans []Loan `json:"loans"`
  128. }
  129. var (
  130. regexen = make(map[string]*regexp.Regexp)
  131. relock sync.Mutex
  132. address = "127.0.0.1:8001"
  133. )
  134. var paths = map[string]string {
  135. "home": "home.tpl",
  136. "terms": "terms.tpl",
  137. "app": "app.tpl",
  138. }
  139. var pages = map[string]Page {
  140. "home": cache("home", "Home"),
  141. "terms": cache("terms", "Terms and Conditions"),
  142. "app": cache("app", "App"),
  143. }
  144. var roles = map[string]int{
  145. "User": 1,
  146. "Manager": 2,
  147. "Admin": 3,
  148. }
  149. // Used to validate claim in JWT token body. Checks if user id is greater than
  150. // zero and time format is valid
  151. func (c UserClaims) Valid() error {
  152. if c.Id < 1 { return errors.New("Invalid id") }
  153. t, err := time.Parse(time.UnixDate, c.Exp)
  154. if err != nil { return err }
  155. if t.Before(time.Now()) { return errors.New("Token expired.") }
  156. return err
  157. }
  158. func cache(name string, title string) Page {
  159. var p = []string{"master.tpl", paths[name]}
  160. tpl := template.Must(template.ParseFiles(p...))
  161. return Page{tpl: tpl,
  162. Title: title,
  163. Name: name,
  164. }
  165. }
  166. func (page Page) Render(w http.ResponseWriter) {
  167. err := page.tpl.Execute(w, page)
  168. if err != nil {
  169. log.Print(err)
  170. }
  171. }
  172. func match(path, pattern string, args *[]string) bool {
  173. relock.Lock()
  174. defer relock.Unlock()
  175. regex := regexen[pattern]
  176. if regex == nil {
  177. regex = regexp.MustCompile("^" + pattern + "$")
  178. regexen[pattern] = regex
  179. }
  180. matches := regex.FindStringSubmatch(path)
  181. if len(matches) <= 0 {
  182. return false
  183. }
  184. *args = matches[1:]
  185. return true
  186. }
  187. func getLoanType(
  188. db *sql.DB,
  189. user int,
  190. branch int,
  191. isUser bool) ([]LoanType, error) {
  192. var loans []LoanType
  193. // Should be changed to specify user
  194. rows, err :=
  195. db.Query(`SELECT * FROM loan_type WHERE user_id = ? AND branch_id = ? ` +
  196. "OR (user_id = 0 AND branch_id = 0)", user, branch)
  197. if err != nil {
  198. return nil, fmt.Errorf("loan_type error: %v", err)
  199. }
  200. defer rows.Close()
  201. for rows.Next() {
  202. var loan LoanType
  203. if err := rows.Scan(
  204. &loan.Id,
  205. &loan.User,
  206. &loan.Branch,
  207. &loan.Name)
  208. err != nil {
  209. log.Printf("Error occured fetching loan: %v", err)
  210. return nil, fmt.Errorf("Error occured fetching loan: %v", err)
  211. }
  212. loans = append(loans, loan)
  213. }
  214. log.Printf("The loans: %v", loans)
  215. return loans, nil
  216. }
  217. func getEstimate(db *sql.DB, id int) (Estimate, error) {
  218. var estimate Estimate
  219. var err error
  220. query := `SELECT e.id, e.user_id, e.transaction,
  221. e.price, e.property, e.occupancy, e.zip, e.pud,
  222. b.id, b.credit_score, b.monthly_income, b.num
  223. FROM estimate e
  224. INNER JOIN borrower b ON e.borrower_id = b.id
  225. WHERE e.id = ?
  226. `
  227. // Inner join should always be valid because a borrower is a required
  228. // foreign key.
  229. row := db.QueryRow(query, id)
  230. if err = row.Scan(
  231. &estimate.Id,
  232. &estimate.User,
  233. &estimate.Transaction,
  234. &estimate.Price,
  235. &estimate.Property,
  236. &estimate.Occupancy,
  237. &estimate.Zip,
  238. &estimate.Pud,
  239. &estimate.Borrower.Id,
  240. &estimate.Borrower.Credit,
  241. &estimate.Borrower.Income,
  242. &estimate.Borrower.Num,
  243. )
  244. err != nil {
  245. return estimate, fmt.Errorf("Estimate scanning error: %v", err)
  246. }
  247. estimate.Loans, err = getLoans(db, estimate.Id)
  248. return estimate, err
  249. }
  250. func getFees(db *sql.DB, loan int) ([]Fee, error) {
  251. var fees []Fee
  252. rows, err := db.Query(
  253. "SELECT * FROM fees " +
  254. "WHERE loan_id = ?",
  255. loan)
  256. if err != nil {
  257. return nil, fmt.Errorf("Fee query error %v", err)
  258. }
  259. defer rows.Close()
  260. for rows.Next() {
  261. var fee Fee
  262. if err := rows.Scan(
  263. &fee.Id,
  264. &fee.LoanId,
  265. &fee.Amount,
  266. &fee.Perc,
  267. &fee.Type,
  268. &fee.Notes,
  269. &fee.Name,
  270. &fee.Category,
  271. )
  272. err != nil {
  273. return nil, fmt.Errorf("Fees scanning error: %v", err)
  274. }
  275. fees = append(fees, fee)
  276. }
  277. return fees, nil
  278. }
  279. func fetchFeesTemp(db *sql.DB, user int, branch int) ([]FeeTemplate, error) {
  280. var fees []FeeTemplate
  281. rows, err := db.Query(
  282. "SELECT * FROM fee_template " +
  283. "WHERE user_id = ? OR branch_id = ?",
  284. user, branch)
  285. if err != nil {
  286. return nil, fmt.Errorf("Fee template query error %v", err)
  287. }
  288. defer rows.Close()
  289. for rows.Next() {
  290. var fee FeeTemplate
  291. if err := rows.Scan(
  292. &fee.Id,
  293. &fee.User,
  294. &fee.Branch,
  295. &fee.Amount,
  296. &fee.Perc,
  297. &fee.Type,
  298. &fee.Notes,
  299. &fee.Name,
  300. &fee.Category,
  301. &fee.Auto)
  302. err != nil {
  303. return nil, fmt.Errorf("FeesTemplate scanning error: %v", err)
  304. }
  305. fees = append(fees, fee)
  306. }
  307. return fees, nil
  308. }
  309. // Fetch fees from the database
  310. func getFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  311. var fees []FeeTemplate
  312. claims, err := getClaims(r)
  313. if err != nil { w.WriteHeader(500); return }
  314. users, err := queryUsers(db, claims.Id)
  315. if err != nil { w.WriteHeader(422); return }
  316. fees, err = fetchFeesTemp(db, claims.Id, users[0].BranchId)
  317. json.NewEncoder(w).Encode(fees)
  318. }
  319. func getMi(db *sql.DB, loan int) (MI, error) {
  320. var mi MI
  321. query := `SELECT
  322. type, label, lender, rate, premium, upfront, five_year_total,
  323. initial_premium, initial_rate, initial_amount
  324. FROM mi WHERE loan_id = ?`
  325. row := db.QueryRow(query, loan)
  326. if err := row.Scan(
  327. &mi.Type,
  328. &mi.Label,
  329. &mi.Lender,
  330. &mi.Rate,
  331. &mi.Premium,
  332. &mi.Upfront,
  333. &mi.FiveYearTotal,
  334. &mi.InitialAllInPremium,
  335. &mi.InitialAllInRate,
  336. &mi.InitialAmount,
  337. )
  338. err != nil {
  339. return mi, err
  340. }
  341. return mi, nil
  342. }
  343. func getLoans(db *sql.DB, estimate int) ([]Loan, error) {
  344. var loans []Loan
  345. query := `SELECT
  346. l.id, l.amount, l.term, l.interest, l.ltv, l.dti, l.hoi,
  347. lt.id, lt.user_id, lt.branch_id, lt.name
  348. FROM loan l INNER JOIN loan_type lt ON l.type_id = lt.id
  349. WHERE l.estimate_id = ?
  350. `
  351. rows, err := db.Query(query, estimate)
  352. if err != nil {
  353. return nil, fmt.Errorf("Loan query error %v", err)
  354. }
  355. defer rows.Close()
  356. for rows.Next() {
  357. var loan Loan
  358. if err := rows.Scan(
  359. &loan.Id,
  360. &loan.Amount,
  361. &loan.Term,
  362. &loan.Interest,
  363. &loan.Ltv,
  364. &loan.Dti,
  365. &loan.Hoi,
  366. &loan.Type.Id,
  367. &loan.Type.User,
  368. &loan.Type.Branch,
  369. &loan.Type.Name,
  370. )
  371. err != nil {
  372. return loans, fmt.Errorf("Loans scanning error: %v", err)
  373. }
  374. mi, err := getMi(db, loan.Id)
  375. if err != nil {
  376. return loans, err
  377. }
  378. loan.Mi = mi
  379. loans = append(loans, loan)
  380. }
  381. return loans, nil
  382. }
  383. func getBorrower(db *sql.DB, id int) (Borrower, error) {
  384. var borrower Borrower
  385. row := db.QueryRow(
  386. "SELECT * FROM borrower " +
  387. "WHERE id = ? LIMIT 1",
  388. id)
  389. if err := row.Scan(
  390. &borrower.Id,
  391. &borrower.Credit,
  392. &borrower.Income,
  393. &borrower.Num,
  394. )
  395. err != nil {
  396. return borrower, fmt.Errorf("Borrower scanning error: %v", err)
  397. }
  398. return borrower, nil
  399. }
  400. // Query Lender APIs and parse responses into MI structs
  401. func fetchMi(db *sql.DB, estimate *Estimate, pos int) []MI {
  402. var err error
  403. var loan Loan = estimate.Loans[pos]
  404. var ltv = func(l float32) string {
  405. switch {
  406. case l > 95: return "LTV97"
  407. case l > 90: return "LTV95"
  408. case l > 85: return "LTV90"
  409. default: return "LTV85"
  410. }
  411. }
  412. var term = func(t int) string {
  413. switch {
  414. case t <= 10: return "A10"
  415. case t <= 15: return "A15"
  416. case t <= 20: return "A20"
  417. case t <= 25: return "A25"
  418. case t <= 30: return "A30"
  419. default: return "A40"
  420. }
  421. }
  422. var propertyCodes = map[string]string {
  423. "Single Attached": "SFO",
  424. "Single Detached": "SFO",
  425. "Condo Lo-rise": "CON",
  426. "Condo Hi-rise": "CON",
  427. }
  428. var purposeCodes = map[int]string {
  429. 1: "PUR",
  430. 2: "RRT",
  431. }
  432. body, err := json.Marshal(map[string]any{
  433. "zipCode": estimate.Zip,
  434. "stateCode": "CA",
  435. "address": "",
  436. "propertyTypeCode": propertyCodes[estimate.Property],
  437. "occupancyTypeCode": "PRS",
  438. "loanPurposeCode": purposeCodes[estimate.Transaction],
  439. "loanAmount": loan.Amount,
  440. "loanToValue": ltv(loan.Ltv),
  441. "amortizationTerm": term(loan.Term),
  442. "loanTypeCode": "FXD",
  443. "duLpDecisionCode": "DAE",
  444. "loanProgramCodes": []any{},
  445. "debtToIncome": loan.Dti,
  446. "wholesaleLoan": 0,
  447. "coveragePercentageCode": "L30",
  448. "productCode": "BPM",
  449. "renewalTypeCode": "CON",
  450. "numberOfBorrowers": 1,
  451. "coBorrowerCreditScores": []any{},
  452. "borrowerCreditScore": strconv.Itoa(estimate.Borrower.Credit),
  453. "masterPolicy": nil,
  454. "selfEmployedIndicator": false,
  455. "armType": "",
  456. "userId": 44504,
  457. })
  458. if err != nil {
  459. log.Printf("Could not marshal NationalMI body: \n%v\n%v\n",
  460. bytes.NewBuffer(body), err)
  461. }
  462. req, err := http.NewRequest("POST",
  463. "https://rate-gps.nationalmi.com/rates/productRateQuote",
  464. bytes.NewBuffer(body))
  465. req.Header.Add("Content-Type", "application/json")
  466. req.AddCookie(&http.Cookie{
  467. Name: "nmirategps_email",
  468. Value: config["NationalMIEmail"]})
  469. resp, err := http.DefaultClient.Do(req)
  470. var res map[string]interface{}
  471. var result []MI
  472. if resp.StatusCode != 200 {
  473. log.Printf("the status: %v\nthe resp: %v\n the req: %v\n the body: %v\n",
  474. resp.Status, resp, req.Body, bytes.NewBuffer(body))
  475. } else {
  476. json.NewDecoder(resp.Body).Decode(&res)
  477. // estimate.Loans[pos].Mi = res
  478. // Parse res into result here
  479. }
  480. return result
  481. }
  482. // Make comparison PDF
  483. func generatePDF(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  484. }
  485. func login(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  486. var id int
  487. var role string
  488. var err error
  489. var user User
  490. json.NewDecoder(r.Body).Decode(&user)
  491. row := db.QueryRow(
  492. `SELECT id, role FROM user WHERE email = ? AND password = sha2(?, 256)`,
  493. user.Email, user.Password,
  494. )
  495. err = row.Scan(&id, &role)
  496. if err != nil {
  497. http.Error(w, "Invalid Credentials.", http.StatusUnauthorized)
  498. return
  499. }
  500. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  501. UserClaims{ Id: id, Role: role,
  502. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  503. tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
  504. if err != nil {
  505. log.Println("Token could not be signed: ", err, tokenStr)
  506. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  507. return
  508. }
  509. cookie := http.Cookie{Name: "skouter",
  510. Value: tokenStr,
  511. Path: "/",
  512. Expires: time.Now().Add(time.Hour * 24)}
  513. http.SetCookie(w, &cookie)
  514. _, err = w.Write([]byte(tokenStr))
  515. if err != nil {
  516. http.Error(w,
  517. "Could not complete token write.",
  518. http.StatusInternalServerError)}
  519. }
  520. func getToken(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  521. claims, err := getClaims(r)
  522. // Will verify existing signature and expiry time
  523. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  524. UserClaims{ Id: claims.Id, Role: claims.Role,
  525. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  526. tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
  527. if err != nil {
  528. log.Println("Token could not be signed: ", err, tokenStr)
  529. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  530. return
  531. }
  532. cookie := http.Cookie{Name: "skouter",
  533. Value: tokenStr,
  534. Path: "/",
  535. Expires: time.Now().Add(time.Hour * 24)}
  536. http.SetCookie(w, &cookie)
  537. _, err = w.Write([]byte(tokenStr))
  538. if err != nil {
  539. http.Error(w,
  540. "Could not complete token write.",
  541. http.StatusInternalServerError)}
  542. }
  543. func getClaims(r *http.Request) (UserClaims, error) {
  544. claims := new(UserClaims)
  545. _, tokenStr, found := strings.Cut(r.Header.Get("Authorization"), " ")
  546. if !found {
  547. return *claims, errors.New("Token not found")
  548. }
  549. // Pull token payload into UserClaims
  550. _, err := jwt.ParseWithClaims(tokenStr, claims,
  551. func(token *jwt.Token) (any, error) {
  552. return []byte(config["JWT_SECRET"]), nil
  553. })
  554. if err != nil {
  555. return *claims, err
  556. }
  557. if err = claims.Valid(); err != nil {
  558. return *claims, err
  559. }
  560. return *claims, nil
  561. }
  562. func guard(r *http.Request, required int) bool {
  563. claims, err := getClaims(r)
  564. if err != nil { return false }
  565. if roles[claims.Role] < required { return false }
  566. return true
  567. }
  568. func queryUsers(db *sql.DB, id int) ( []User, error ) {
  569. var users []User
  570. var query string
  571. var rows *sql.Rows
  572. var err error
  573. query = `SELECT
  574. u.id,
  575. u.email,
  576. u.first_name,
  577. u.last_name,
  578. u.branch_id,
  579. u.country,
  580. u.title,
  581. u.status,
  582. u.verified,
  583. u.role
  584. FROM user u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
  585. `
  586. rows, err = db.Query(query, id)
  587. if err != nil {
  588. return users, err
  589. }
  590. defer rows.Close()
  591. for rows.Next() {
  592. var user User
  593. if err := rows.Scan(
  594. &user.Id,
  595. &user.Email,
  596. &user.FirstName,
  597. &user.LastName,
  598. &user.BranchId,
  599. &user.Country,
  600. &user.Title,
  601. &user.Status,
  602. &user.Verified,
  603. &user.Role,
  604. )
  605. err != nil {
  606. return users, err
  607. }
  608. users = append(users, user)
  609. }
  610. // Prevents runtime panics
  611. if len(users) == 0 { return users, errors.New("User not found.") }
  612. return users, 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 createEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  743. var estimate Estimate
  744. err := json.NewDecoder(r.Body).Decode(&estimate)
  745. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  746. json.NewEncoder(w).Encode(estimate)
  747. }
  748. func checkConventional(l Loan, b Borrower) error {
  749. if b.Credit < 620 {
  750. return errors.New("Credit score too low for conventional loan")
  751. }
  752. // Buyer needs to put down 20% to avoid mortgage insurance
  753. if (l.Ltv > 80 && l.Mi.Rate == 0) {
  754. return errors.New(fmt.Sprintln(
  755. l.Name,
  756. "down payment must be 20% to avoid insurance",
  757. ))
  758. }
  759. return nil
  760. }
  761. func checkFHA(l Loan, b Borrower) error {
  762. if b.Credit < 500 {
  763. return errors.New("Credit score too low for FHA loan")
  764. }
  765. if (l.Ltv > 96.5) {
  766. return errors.New("FHA down payment must be at least 3.5%")
  767. }
  768. if (b.Credit < 580 && l.Ltv > 90) {
  769. return errors.New("FHA down payment must be at least 10%")
  770. }
  771. // Debt-to-income must be below 45% if credit score is below 580.
  772. if (b.Credit < 580 && l.Dti > 45) {
  773. return errors.New(fmt.Sprintln(
  774. l.Name, "debt to income is too high for credit score.",
  775. ))
  776. }
  777. return nil
  778. }
  779. // Should also check loan amount limit maybe with an API.
  780. func checkEstimate(e Estimate) error {
  781. if e.Property == "" { return errors.New("Empty property type") }
  782. if e.Price == 0 { return errors.New("Empty property price") }
  783. if e.Borrower.Num == 0 {
  784. return errors.New("Missing number of borrowers")
  785. }
  786. if e.Borrower.Credit == 0 {
  787. return errors.New("Missing borrower credit score")
  788. }
  789. if e.Borrower.Income == 0 {
  790. return errors.New("Missing borrower credit income")
  791. }
  792. for _, l := range e.Loans {
  793. if l.Amount == 0 {
  794. return errors.New(fmt.Sprintln(l.Name, "loan amount cannot be zero"))
  795. }
  796. if l.Term == 0 {
  797. return errors.New(fmt.Sprintln(l.Name, "loan term cannot be zero"))
  798. }
  799. if l.Interest == 0 {
  800. return errors.New(fmt.Sprintln(l.Name, "loan interest cannot be zero"))
  801. }
  802. // Can be used to check rules for specific loan types
  803. var err error
  804. switch l.Type.Name {
  805. case "Conventional":
  806. err = checkConventional(l, e.Borrower)
  807. case "FHA":
  808. err = checkFHA(l, e.Borrower)
  809. case "VA":
  810. err = checkConventional(l, e.Borrower)
  811. case "USDA":
  812. err = checkConventional(l, e.Borrower)
  813. }
  814. if err != nil { return err }
  815. }
  816. println("done")
  817. return nil
  818. }
  819. func validateEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  820. var estimate Estimate
  821. err := json.NewDecoder(r.Body).Decode(&estimate)
  822. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  823. err = checkEstimate(estimate)
  824. if err != nil { http.Error(w, err.Error(), 406); return }
  825. }
  826. func api(w http.ResponseWriter, r *http.Request) {
  827. var args []string
  828. p := r.URL.Path
  829. db, err := sql.Open("mysql",
  830. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
  831. config["DBUser"],
  832. config["DBPass"]))
  833. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  834. err = db.Ping()
  835. if err != nil {
  836. fmt.Println("Bad database configuration: %v\n", err)
  837. panic(err)
  838. // maybe os.Exit(1) instead
  839. }
  840. switch {
  841. case match(p, "/api/login", &args) &&
  842. r.Method == http.MethodPost:
  843. login(w, db, r)
  844. case match(p, "/api/token", &args) &&
  845. r.Method == http.MethodGet && guard(r, 1):
  846. getToken(w, db, r)
  847. case match(p, "/api/users", &args) && // Array of all users
  848. r.Method == http.MethodGet && guard(r, 3):
  849. getUsers(w, db, r)
  850. case match(p, "/api/user", &args) &&
  851. r.Method == http.MethodGet && guard(r, 1):
  852. getUser(w, db, r)
  853. case match(p, "/api/user", &args) &&
  854. r.Method == http.MethodPost &&
  855. guard(r, 3):
  856. createUser(w, db, r)
  857. case match(p, "/api/user", &args) &&
  858. r.Method == http.MethodPatch &&
  859. guard(r, 3): // For admin to modify any user
  860. patchUser(w, db, r)
  861. case match(p, "/api/user", &args) &&
  862. r.Method == http.MethodPatch &&
  863. guard(r, 2): // For employees to modify own accounts
  864. patchSelf(w, db, r)
  865. case match(p, "/api/user", &args) &&
  866. r.Method == http.MethodDelete &&
  867. guard(r, 3):
  868. deleteUser(w, db, r)
  869. case match(p, "/api/fees", &args) &&
  870. r.Method == http.MethodGet &&
  871. guard(r, 1):
  872. getFeesTemp(w, db, r)
  873. case match(p, "/api/estimate", &args) &&
  874. r.Method == http.MethodPost &&
  875. guard(r, 1):
  876. createEstimate(w, db, r)
  877. case match(p, "/api/estimate/validate", &args) &&
  878. r.Method == http.MethodPost &&
  879. guard(r, 1):
  880. validateEstimate(w, db, r)
  881. }
  882. db.Close()
  883. }
  884. func route(w http.ResponseWriter, r *http.Request) {
  885. var page Page
  886. var args []string
  887. p := r.URL.Path
  888. switch {
  889. case r.Method == "GET" && match(p, "/", &args):
  890. page = pages[ "home" ]
  891. case match(p, "/terms", &args):
  892. page = pages[ "terms" ]
  893. case match(p, "/app", &args):
  894. page = pages[ "app" ]
  895. default:
  896. http.NotFound(w, r)
  897. return
  898. }
  899. page.Render(w)
  900. }
  901. func main() {
  902. files := http.FileServer(http.Dir(""))
  903. http.Handle("/assets/", files)
  904. http.HandleFunc("/api/", api)
  905. http.HandleFunc("/", route)
  906. log.Fatal(http.ListenAndServe(address, nil))
  907. }