Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

1338 wiersze
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. }