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

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