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

1671 lines
36 KiB

  1. package main
  2. import (
  3. "net/http"
  4. "net/mail"
  5. "log"
  6. "sync"
  7. "regexp"
  8. "html/template"
  9. "database/sql"
  10. _ "github.com/go-sql-driver/mysql"
  11. "fmt"
  12. "encoding/json"
  13. "strconv"
  14. "bytes"
  15. "time"
  16. "errors"
  17. "strings"
  18. "math"
  19. // pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
  20. "github.com/golang-jwt/jwt/v4"
  21. )
  22. type User struct {
  23. Id int `json:"id"`
  24. Email string `json:"email"`
  25. FirstName string `json:"firstName"`
  26. LastName string `json:"lastName"`
  27. BranchId int `json:"branchId"`
  28. Status string `json:"status"`
  29. Country string `json:"country"`
  30. Title string `json:"title"`
  31. Verified bool `json:"verified"`
  32. Role string `json:"role"`
  33. Password string `json:"password,omitempty"`
  34. }
  35. type UserClaims struct {
  36. Id int `json:"id"`
  37. Role string `json:"role"`
  38. Exp string `json:"exp"`
  39. }
  40. type Page struct {
  41. tpl *template.Template
  42. Title string
  43. Name string
  44. }
  45. type Borrower struct {
  46. Id int `json:"id"`
  47. Credit int `json:"credit"`
  48. Income int `json:"income"`
  49. Num int `json:"num"`
  50. }
  51. type FeeTemplate struct {
  52. Id int `json:"id"`
  53. User int `json:"user"`
  54. Branch int `json:"branch"`
  55. Amount int `json:"amount"`
  56. Perc float32 `json:"perc"`
  57. Type string `json:"type"`
  58. Notes string `json:"notes"`
  59. Name string `json:"name"`
  60. Category string `json:"category"`
  61. Auto bool `json:"auto"`
  62. }
  63. type Fee struct {
  64. Id int `json:"id"`
  65. LoanId int `json:"loan_id"`
  66. Amount int `json:"amount"`
  67. Perc float32 `json:"perc"`
  68. Type string `json:"type"`
  69. Notes string `json:"notes"`
  70. Name string `json:"name"`
  71. Category string `json:"category"`
  72. }
  73. type LoanType struct {
  74. Id int `json:"id"`
  75. User int `json:"user"`
  76. Branch int `json:"branch"`
  77. Name string `json:"name"`
  78. }
  79. type Loan struct {
  80. Id int `json:"id"`
  81. EstimateId int `json:"estimateId"`
  82. Type LoanType `json:"type"`
  83. Amount int `json:"amount"`
  84. Amortization string `json:"amortization"`
  85. Term int `json:"term"`
  86. Ltv float32 `json:"ltv"`
  87. Dti float32 `json:"dti"`
  88. Hoi int `json:"hoi"`
  89. Hazard int `json:"hazard"`
  90. Tax int `json:"tax"`
  91. Interest float32 `json:"interest"`
  92. Mi MI `json:"mi"`
  93. Fees []Fee `json:"fees"`
  94. Name string `json:"title"`
  95. }
  96. type MI struct {
  97. Type string `json:"user"`
  98. Label string `json:"label"`
  99. Lender string `json:"lender"`
  100. Rate float32 `json:"rate"`
  101. Premium float32 `json:"premium"`
  102. Upfront float32 `json:"upfront"`
  103. Monthly bool `json:"monthly"`
  104. FiveYearTotal float32 `json:"fiveYearTotal"`
  105. InitialAllInPremium float32 `json:"initialAllInPremium"`
  106. InitialAllInRate float32 `json:"initialAllInRate"`
  107. InitialAmount float32 `json:"initialAmount"`
  108. }
  109. type Result struct {
  110. Id int `json:"id"`
  111. LoanId int `json:"loanId"`
  112. LoanPayment int `json:"loanPayment"`
  113. TotalMonthly int `json:"totalMonthly"`
  114. TotalFees int `json:"totalFees"`
  115. TotalCredits int `json:"totalCredits"`
  116. CashToClose int `json:"cashToClose"`
  117. }
  118. type Estimate struct {
  119. Id int `json:"id"`
  120. User int `json:"user"`
  121. Borrower Borrower `json:"borrower"`
  122. Transaction string `json:"transaction"`
  123. Price int `json:"price"`
  124. Property string `json:"property"`
  125. Occupancy string `json:"occupancy"`
  126. Zip string `json:"zip"`
  127. Pud bool `json:"pud"`
  128. Loans []Loan `json:"loans"`
  129. Results []Result `json:"results"`
  130. }
  131. var (
  132. regexen = make(map[string]*regexp.Regexp)
  133. relock sync.Mutex
  134. address = "127.0.0.1:8001"
  135. )
  136. var paths = map[string]string {
  137. "home": "views/home.tpl",
  138. "terms": "views/terms.tpl",
  139. "app": "views/app.tpl",
  140. "test": "views/test.tpl",
  141. }
  142. var pages = map[string]Page {
  143. "home": cache("home", "Home"),
  144. "terms": cache("terms", "Terms and Conditions"),
  145. "test": cache("test", "PDF test"),
  146. "app": cache("app", "App"),
  147. }
  148. var roles = map[string]int{
  149. "User": 1,
  150. "Manager": 2,
  151. "Admin": 3,
  152. }
  153. // Used to validate claim in JWT token body. Checks if user id is greater than
  154. // zero and time format is valid
  155. func (c UserClaims) Valid() error {
  156. if c.Id < 1 { return errors.New("Invalid id") }
  157. t, err := time.Parse(time.UnixDate, c.Exp)
  158. if err != nil { return err }
  159. if t.Before(time.Now()) { return errors.New("Token expired.") }
  160. return err
  161. }
  162. func cache(name string, title string) Page {
  163. var p = []string{"views/master.tpl", paths[name]}
  164. tpl := template.Must(template.ParseFiles(p...))
  165. return Page{tpl: tpl,
  166. Title: title,
  167. Name: name,
  168. }
  169. }
  170. func (page Page) Render(w http.ResponseWriter) {
  171. err := page.tpl.Execute(w, page)
  172. if err != nil {
  173. log.Print(err)
  174. }
  175. }
  176. func match(path, pattern string, args *[]string) bool {
  177. relock.Lock()
  178. defer relock.Unlock()
  179. regex := regexen[pattern]
  180. if regex == nil {
  181. regex = regexp.MustCompile("^" + pattern + "$")
  182. regexen[pattern] = regex
  183. }
  184. matches := regex.FindStringSubmatch(path)
  185. if len(matches) <= 0 {
  186. return false
  187. }
  188. *args = matches[1:]
  189. return true
  190. }
  191. func makeResults(estimate Estimate) ([]Result) {
  192. var results []Result
  193. amortize := func(principle float64, rate float64, periods float64) int {
  194. exp := math.Pow(1+rate, periods)
  195. return int(principle * rate * exp / (exp - 1))
  196. }
  197. for _, loan := range estimate.Loans {
  198. var result Result
  199. // Monthly payments use amortized loan payment formula plus monthly MI,
  200. // plus all other recurring fees
  201. result.LoanPayment = amortize(float64(loan.Amount),
  202. float64(loan.Interest / 100 / 12),
  203. float64(loan.Term * 12))
  204. result.TotalMonthly = result.LoanPayment + loan.Hoi + loan.Tax + loan.Hazard
  205. if loan.Mi.Monthly {
  206. result.TotalMonthly = result.TotalMonthly +
  207. int(loan.Mi.Rate/100*float32(loan.Amount))
  208. }
  209. for i := range loan.Fees {
  210. if loan.Fees[i].Amount > 0 {
  211. result.TotalFees = result.TotalFees + loan.Fees[i].Amount
  212. } else {
  213. result.TotalCredits = result.TotalCredits + loan.Fees[i].Amount
  214. }
  215. }
  216. result.CashToClose =
  217. result.TotalFees + result.TotalCredits + (estimate.Price - loan.Amount)
  218. result.LoanId = loan.Id
  219. results = append(results, result)
  220. }
  221. return results
  222. }
  223. func summarize(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  224. var estimate Estimate
  225. err := json.NewDecoder(r.Body).Decode(&estimate)
  226. if err != nil { http.Error(w, "Invalid estimate.", 422); return }
  227. results := makeResults(estimate)
  228. json.NewEncoder(w).Encode(results)
  229. }
  230. func getLoanType( db *sql.DB, id int) (LoanType, error) {
  231. types, err := getLoanTypes(db, id, 0, 0)
  232. if err != nil { return LoanType{Id: id}, err }
  233. if len(types) == 0 {
  234. return LoanType{Id: id}, errors.New("No type with that id")
  235. }
  236. return types[0], nil
  237. }
  238. func getLoanTypes( db *sql.DB, id int, user int, branch int ) (
  239. []LoanType, error) {
  240. var loans []LoanType
  241. var query = `SELECT
  242. id,
  243. user_id,
  244. branch_id,
  245. name
  246. FROM loan_type WHERE loan_type.id = CASE @e := ? WHEN 0 THEN id ELSE @e END
  247. OR
  248. loan_type.user_id = CASE @e := ? WHEN 0 THEN id ELSE @e END
  249. OR
  250. loan_type.branch_id = CASE @e := ? WHEN 0 THEN id ELSE @e END`
  251. // Should be changed to specify user
  252. rows, err := db.Query(query, id, user, branch)
  253. if err != nil {
  254. return nil, fmt.Errorf("loan_type error: %v", err)
  255. }
  256. defer rows.Close()
  257. for rows.Next() {
  258. var loan LoanType
  259. if err := rows.Scan(
  260. &loan.Id,
  261. &loan.User,
  262. &loan.Branch,
  263. &loan.Name)
  264. err != nil {
  265. log.Printf("Error occured fetching loan: %v", err)
  266. return nil, fmt.Errorf("Error occured fetching loan: %v", err)
  267. }
  268. loans = append(loans, loan)
  269. }
  270. return loans, nil
  271. }
  272. func getFees(db *sql.DB, loan int) ([]Fee, error) {
  273. var fees []Fee
  274. query := `SELECT id, loan_id, amount, perc, type, notes, name, category
  275. FROM fee
  276. WHERE loan_id = ?`
  277. rows, err := db.Query(query, loan)
  278. if err != nil {
  279. return nil, fmt.Errorf("Fee query error %v", err)
  280. }
  281. defer rows.Close()
  282. for rows.Next() {
  283. var fee Fee
  284. if err := rows.Scan(
  285. &fee.Id,
  286. &fee.LoanId,
  287. &fee.Amount,
  288. &fee.Perc,
  289. &fee.Type,
  290. &fee.Notes,
  291. &fee.Name,
  292. &fee.Category,
  293. )
  294. err != nil {
  295. return nil, fmt.Errorf("Fees scanning error: %v", err)
  296. }
  297. fees = append(fees, fee)
  298. }
  299. return fees, nil
  300. }
  301. func fetchFeesTemp(db *sql.DB, user int, branch int) ([]FeeTemplate, error) {
  302. var fees []FeeTemplate
  303. query := `SELECT
  304. id, user_id, COALESCE(branch_id, 0), amount, perc, type, notes, name,
  305. category, auto
  306. FROM fee_template
  307. WHERE user_id = ? OR branch_id = ?
  308. `
  309. rows, err := db.Query(query, user, branch)
  310. if err != nil {
  311. return nil, fmt.Errorf("Fee template query error %v", err)
  312. }
  313. defer rows.Close()
  314. for rows.Next() {
  315. var fee FeeTemplate
  316. if err := rows.Scan(
  317. &fee.Id,
  318. &fee.User,
  319. &fee.Branch,
  320. &fee.Amount,
  321. &fee.Perc,
  322. &fee.Type,
  323. &fee.Notes,
  324. &fee.Name,
  325. &fee.Category,
  326. &fee.Auto)
  327. err != nil {
  328. return nil, fmt.Errorf("FeesTemplate scanning error: %v", err)
  329. }
  330. fees = append(fees, fee)
  331. }
  332. return fees, nil
  333. }
  334. // Fetch fees from the database
  335. func getFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  336. var fees []FeeTemplate
  337. claims, err := getClaims(r)
  338. if err != nil { w.WriteHeader(500); return }
  339. users, err := queryUsers(db, claims.Id)
  340. if err != nil { w.WriteHeader(422); return }
  341. fees, err = fetchFeesTemp(db, claims.Id, users[0].BranchId)
  342. json.NewEncoder(w).Encode(fees)
  343. }
  344. // Fetch fees from the database
  345. func createFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  346. var fee FeeTemplate
  347. var query string
  348. var row *sql.Row
  349. var err error
  350. claims, err := getClaims(r)
  351. // var id int // Inserted estimate's id
  352. err = json.NewDecoder(r.Body).Decode(&fee)
  353. if err != nil { w.WriteHeader(422); return }
  354. query = `INSERT INTO fee_template
  355. (
  356. user_id,
  357. branch_id,
  358. amount,
  359. perc,
  360. type,
  361. notes,
  362. name,
  363. auto
  364. )
  365. VALUES (?, NULL, ?, ?, ?, ?, ?, ?)
  366. RETURNING id
  367. `
  368. row = db.QueryRow(query,
  369. claims.Id,
  370. fee.Amount,
  371. fee.Perc,
  372. fee.Type,
  373. fee.Notes,
  374. fee.Name,
  375. fee.Auto,
  376. )
  377. err = row.Scan(&fee.Id)
  378. if err != nil { w.WriteHeader(500); return }
  379. json.NewEncoder(w).Encode(fee)
  380. }
  381. // Fetch fees from the database
  382. func deleteFeeTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  383. var fee FeeTemplate
  384. var query string
  385. var err error
  386. // claims, err := getClaims(r)
  387. // var id int // Inserted estimate's id
  388. err = json.NewDecoder(r.Body).Decode(&fee)
  389. if err != nil { w.WriteHeader(422); return }
  390. query = `DELETE FROM fee_template WHERE id = ?`
  391. _, err = db.Exec(query, fee.Id)
  392. if err != nil { w.WriteHeader(500); return }
  393. }
  394. func getMi(db *sql.DB, loan int) (MI, error) {
  395. var mi MI
  396. query := `SELECT
  397. type, label, lender, rate, premium, upfront, five_year_total,
  398. initial_premium, initial_rate, initial_amount
  399. FROM mi WHERE loan_id = ?`
  400. rows, err := db.Query(query, loan)
  401. if err != nil { return mi, err }
  402. defer rows.Close()
  403. if (!rows.Next()) { return mi, nil }
  404. if err := rows.Scan(
  405. &mi.Type,
  406. &mi.Label,
  407. &mi.Lender,
  408. &mi.Rate,
  409. &mi.Premium,
  410. &mi.Upfront,
  411. &mi.FiveYearTotal,
  412. &mi.InitialAllInPremium,
  413. &mi.InitialAllInRate,
  414. &mi.InitialAmount,
  415. )
  416. err != nil {
  417. return mi, err
  418. }
  419. return mi, nil
  420. }
  421. func getBorrower(db *sql.DB, id int) (Borrower, error) {
  422. var borrower Borrower
  423. row := db.QueryRow(
  424. "SELECT * FROM borrower " +
  425. "WHERE id = ? LIMIT 1",
  426. id)
  427. if err := row.Scan(
  428. &borrower.Id,
  429. &borrower.Credit,
  430. &borrower.Income,
  431. &borrower.Num,
  432. )
  433. err != nil {
  434. return borrower, fmt.Errorf("Borrower scanning error: %v", err)
  435. }
  436. return borrower, nil
  437. }
  438. // Query Lender APIs and parse responses into MI structs
  439. func fetchMi(db *sql.DB, estimate *Estimate, pos int) []MI {
  440. var err error
  441. var loan Loan = estimate.Loans[pos]
  442. var ltv = func(l float32) string {
  443. switch {
  444. case l > 95: return "LTV97"
  445. case l > 90: return "LTV95"
  446. case l > 85: return "LTV90"
  447. default: return "LTV85"
  448. }
  449. }
  450. var term = func(t int) string {
  451. switch {
  452. case t <= 10: return "A10"
  453. case t <= 15: return "A15"
  454. case t <= 20: return "A20"
  455. case t <= 25: return "A25"
  456. case t <= 30: return "A30"
  457. default: return "A40"
  458. }
  459. }
  460. var propertyCodes = map[string]string {
  461. "Single Attached": "SFO",
  462. "Single Detached": "SFO",
  463. "Condo Lo-rise": "CON",
  464. "Condo Hi-rise": "CON",
  465. }
  466. var purposeCodes = map[string]string {
  467. "Purchase": "PUR",
  468. "Refinance": "RRT",
  469. }
  470. body, err := json.Marshal(map[string]any{
  471. "zipCode": estimate.Zip,
  472. "stateCode": "CA",
  473. "address": "",
  474. "propertyTypeCode": propertyCodes[estimate.Property],
  475. "occupancyTypeCode": "PRS",
  476. "loanPurposeCode": purposeCodes[estimate.Transaction],
  477. "loanAmount": loan.Amount,
  478. "loanToValue": ltv(loan.Ltv),
  479. "amortizationTerm": term(loan.Term),
  480. "loanTypeCode": "FXD",
  481. "duLpDecisionCode": "DAE",
  482. "loanProgramCodes": []any{},
  483. "debtToIncome": loan.Dti,
  484. "wholesaleLoan": 0,
  485. "coveragePercentageCode": "L30",
  486. "productCode": "BPM",
  487. "renewalTypeCode": "CON",
  488. "numberOfBorrowers": 1,
  489. "coBorrowerCreditScores": []any{},
  490. "borrowerCreditScore": strconv.Itoa(estimate.Borrower.Credit),
  491. "masterPolicy": nil,
  492. "selfEmployedIndicator": false,
  493. "armType": "",
  494. "userId": 44504,
  495. })
  496. if err != nil {
  497. log.Printf("Could not marshal NationalMI body: \n%v\n%v\n",
  498. bytes.NewBuffer(body), err)
  499. }
  500. req, err := http.NewRequest("POST",
  501. "https://rate-gps.nationalmi.com/rates/productRateQuote",
  502. bytes.NewBuffer(body))
  503. req.Header.Add("Content-Type", "application/json")
  504. req.AddCookie(&http.Cookie{
  505. Name: "nmirategps_email",
  506. Value: config["NationalMIEmail"]})
  507. resp, err := http.DefaultClient.Do(req)
  508. var res map[string]interface{}
  509. var result []MI
  510. if resp.StatusCode != 200 {
  511. log.Printf("the status: %v\nthe resp: %v\n the req: %v\n the body: %v\n",
  512. resp.Status, resp, req.Body, bytes.NewBuffer(body))
  513. } else {
  514. json.NewDecoder(resp.Body).Decode(&res)
  515. // estimate.Loans[pos].Mi = res
  516. // Parse res into result here
  517. }
  518. return result
  519. }
  520. // Make comparison PDF
  521. func generatePDF(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  522. }
  523. func login(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  524. var id int
  525. var role string
  526. var err error
  527. var user User
  528. json.NewDecoder(r.Body).Decode(&user)
  529. row := db.QueryRow(
  530. `SELECT id, role FROM user WHERE email = ? AND password = sha2(?, 256)`,
  531. user.Email, user.Password,
  532. )
  533. err = row.Scan(&id, &role)
  534. if err != nil {
  535. http.Error(w, "Invalid Credentials.", http.StatusUnauthorized)
  536. return
  537. }
  538. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  539. UserClaims{ Id: id, Role: role,
  540. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  541. tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
  542. if err != nil {
  543. log.Println("Token could not be signed: ", err, tokenStr)
  544. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  545. return
  546. }
  547. cookie := http.Cookie{Name: "skouter",
  548. Value: tokenStr,
  549. Path: "/",
  550. Expires: time.Now().Add(time.Hour * 24)}
  551. http.SetCookie(w, &cookie)
  552. _, err = w.Write([]byte(tokenStr))
  553. if err != nil {
  554. http.Error(w,
  555. "Could not complete token write.",
  556. http.StatusInternalServerError)}
  557. }
  558. func getToken(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  559. claims, err := getClaims(r)
  560. // Will verify existing signature and expiry time
  561. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  562. UserClaims{ Id: claims.Id, Role: claims.Role,
  563. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  564. tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
  565. if err != nil {
  566. log.Println("Token could not be signed: ", err, tokenStr)
  567. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  568. return
  569. }
  570. cookie := http.Cookie{Name: "skouter",
  571. Value: tokenStr,
  572. Path: "/",
  573. Expires: time.Now().Add(time.Hour * 24)}
  574. http.SetCookie(w, &cookie)
  575. _, err = w.Write([]byte(tokenStr))
  576. if err != nil {
  577. http.Error(w,
  578. "Could not complete token write.",
  579. http.StatusInternalServerError)}
  580. }
  581. func getClaims(r *http.Request) (UserClaims, error) {
  582. claims := new(UserClaims)
  583. _, tokenStr, found := strings.Cut(r.Header.Get("Authorization"), " ")
  584. if !found {
  585. return *claims, errors.New("Token not found")
  586. }
  587. // Pull token payload into UserClaims
  588. _, err := jwt.ParseWithClaims(tokenStr, claims,
  589. func(token *jwt.Token) (any, error) {
  590. return []byte(config["JWT_SECRET"]), nil
  591. })
  592. if err != nil {
  593. return *claims, err
  594. }
  595. if err = claims.Valid(); err != nil {
  596. return *claims, err
  597. }
  598. return *claims, nil
  599. }
  600. func guard(r *http.Request, required int) bool {
  601. claims, err := getClaims(r)
  602. if err != nil { return false }
  603. if roles[claims.Role] < required { return false }
  604. return true
  605. }
  606. func queryUsers(db *sql.DB, id int) ( []User, error ) {
  607. var users []User
  608. var query string
  609. var rows *sql.Rows
  610. var err error
  611. query = `SELECT
  612. u.id,
  613. u.email,
  614. u.first_name,
  615. u.last_name,
  616. u.branch_id,
  617. u.country,
  618. u.title,
  619. u.status,
  620. u.verified,
  621. u.role
  622. FROM user u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
  623. `
  624. rows, err = db.Query(query, id)
  625. if err != nil {
  626. return users, err
  627. }
  628. defer rows.Close()
  629. for rows.Next() {
  630. var user User
  631. if err := rows.Scan(
  632. &user.Id,
  633. &user.Email,
  634. &user.FirstName,
  635. &user.LastName,
  636. &user.BranchId,
  637. &user.Country,
  638. &user.Title,
  639. &user.Status,
  640. &user.Verified,
  641. &user.Role,
  642. )
  643. err != nil {
  644. return users, err
  645. }
  646. users = append(users, user)
  647. }
  648. // Prevents runtime panics
  649. if len(users) == 0 { return users, errors.New("User not found.") }
  650. return users, nil
  651. }
  652. func insertResults(db *sql.DB, results []Result) (error){
  653. var query string
  654. var row *sql.Row
  655. var err error
  656. query = `INSERT INTO estimate_result
  657. (
  658. loan_id,
  659. loan_payment,
  660. total_monthly,
  661. total_fees,
  662. total_credits,
  663. cash_to_close
  664. )
  665. VALUES (?, ?, ?, ?, ?, ?, ?)
  666. RETURNING id
  667. `
  668. for i := range results {
  669. db.QueryRow(query,
  670. results[i].LoanId,
  671. results[i].LoanPayment,
  672. results[i].TotalMonthly,
  673. results[i].TotalFees,
  674. results[i].TotalCredits,
  675. results[i].CashToClose,
  676. )
  677. err = row.Scan(&results[i].Id)
  678. if err != nil { return err }
  679. }
  680. return nil
  681. }
  682. func insertUser(db *sql.DB, user User) (User, error){
  683. var query string
  684. var row *sql.Row
  685. var err error
  686. var id int // Inserted user's id
  687. query = `INSERT INTO user
  688. (
  689. email,
  690. first_name,
  691. last_name,
  692. password,
  693. created,
  694. role,
  695. verified,
  696. last_login
  697. )
  698. VALUES (?, ?, ?, sha2(?, 256), NOW(), ?, ?, NOW())
  699. RETURNING id
  700. `
  701. row = db.QueryRow(query,
  702. user.Email,
  703. user.FirstName,
  704. user.LastName,
  705. user.Password,
  706. user.Role,
  707. user.Verified,
  708. )
  709. err = row.Scan(&id)
  710. if err != nil { return User{}, err }
  711. users, err := queryUsers(db, id)
  712. if err != nil { return User{}, err }
  713. return users[0], nil
  714. }
  715. func updateUser(user User, db *sql.DB) error {
  716. query := `
  717. UPDATE user
  718. SET
  719. email = CASE @e := ? WHEN '' THEN email ELSE @e END,
  720. first_name = CASE @fn := ? WHEN '' THEN first_name ELSE @fn END,
  721. last_name = CASE @ln := ? WHEN '' THEN last_name ELSE @ln END,
  722. role = CASE @r := ? WHEN '' THEN role ELSE @r END,
  723. password = CASE @p := ? WHEN '' THEN password ELSE sha2(@p, 256) END
  724. WHERE id = ?
  725. `
  726. _, err := db.Exec(query,
  727. user.Email,
  728. user.FirstName,
  729. user.LastName,
  730. user.Role,
  731. user.Password,
  732. user.Id,
  733. )
  734. return err
  735. }
  736. func getUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  737. claims, err := getClaims(r)
  738. if err != nil { w.WriteHeader(500); return }
  739. users, err := queryUsers(db, claims.Id)
  740. if err != nil { w.WriteHeader(422); log.Println(err); return }
  741. json.NewEncoder(w).Encode(users)
  742. }
  743. func getUsers(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  744. users, err := queryUsers(db, 0)
  745. if err != nil {
  746. w.WriteHeader(http.StatusInternalServerError)
  747. return
  748. }
  749. json.NewEncoder(w).Encode(users)
  750. }
  751. // Updates a user using only specified values in the JSON body
  752. func patchUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  753. var user User
  754. err := json.NewDecoder(r.Body).Decode(&user)
  755. _, err = mail.ParseAddress(user.Email)
  756. if err != nil { http.Error(w, "Invalid email.", 422); return }
  757. if roles[user.Role] == 0 {
  758. http.Error(w, "Invalid role.", 422)
  759. return
  760. }
  761. err = updateUser(user, db)
  762. if err != nil { http.Error(w, "Bad form values.", 422); return }
  763. users, err := queryUsers(db, user.Id)
  764. if err != nil { http.Error(w, "Bad form values.", 422); return }
  765. json.NewEncoder(w).Encode(users[0])
  766. }
  767. // Update specified fields of the user specified in the claim
  768. func patchSelf(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  769. claim, err := getClaims(r)
  770. var user User
  771. json.NewDecoder(r.Body).Decode(&user)
  772. // First check that the target user to be updated is the same as the claim id, and
  773. // their role is unchanged.
  774. if err != nil || claim.Id != user.Id {
  775. http.Error(w, "Target user's id does not match claim.", 401)
  776. return
  777. }
  778. if claim.Role != user.Role && user.Role != "" {
  779. http.Error(w, "Administrator required to escalate role.", 401)
  780. return
  781. }
  782. patchUser(w, db, r)
  783. }
  784. func deleteUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  785. var user User
  786. err := json.NewDecoder(r.Body).Decode(&user)
  787. if err != nil {
  788. http.Error(w, "Invalid fields.", 422)
  789. return
  790. }
  791. query := `DELETE FROM user WHERE id = ?`
  792. _, err = db.Exec(query, user.Id)
  793. if err != nil {
  794. http.Error(w, "Could not delete.", 500)
  795. }
  796. }
  797. func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  798. var user User
  799. err := json.NewDecoder(r.Body).Decode(&user)
  800. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  801. _, err = mail.ParseAddress(user.Email)
  802. if err != nil { http.Error(w, "Invalid email.", 422); return }
  803. if roles[user.Role] == 0 {
  804. http.Error(w, "Invalid role.", 422)
  805. }
  806. user, err = insertUser(db, user)
  807. if err != nil { http.Error(w, "Error creating user.", 422); return }
  808. json.NewEncoder(w).Encode(user)
  809. }
  810. func queryBorrower(db *sql.DB, id int) ( Borrower, error ) {
  811. var borrower Borrower
  812. var query string
  813. var err error
  814. query = `SELECT
  815. l.id,
  816. l.credit_score,
  817. l.num,
  818. l.monthly_income
  819. FROM borrower l WHERE l.id = ?
  820. `
  821. row := db.QueryRow(query, id)
  822. err = row.Scan(
  823. borrower.Id,
  824. borrower.Credit,
  825. borrower.Num,
  826. borrower.Income,
  827. )
  828. if err != nil {
  829. return borrower, err
  830. }
  831. return borrower, nil
  832. }
  833. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  834. func getResults(db *sql.DB, e int, id int) ( []Result, error ) {
  835. var results []Result
  836. var query string
  837. var rows *sql.Rows
  838. var err error
  839. query = `SELECT
  840. r.id,
  841. loan_id,
  842. loan_payment,
  843. total_monthly,
  844. total_fees,
  845. total_credits,
  846. cash_to_close
  847. FROM estimate_result r
  848. INNER JOIN loan
  849. ON r.loan_id = loan.id
  850. WHERE r.id = CASE @e := ? WHEN 0 THEN r.id ELSE @e END
  851. AND loan.estimate_id = ?
  852. `
  853. rows, err = db.Query(query, id, e)
  854. if err != nil {
  855. return results, err
  856. }
  857. defer rows.Close()
  858. for rows.Next() {
  859. var result Result
  860. if err := rows.Scan(
  861. &result.Id,
  862. &result.LoanId,
  863. &result.LoanPayment,
  864. &result.TotalMonthly,
  865. &result.TotalFees,
  866. &result.TotalCredits,
  867. &result.CashToClose,
  868. )
  869. err != nil {
  870. return results, err
  871. }
  872. results = append(results, result)
  873. }
  874. // Prevents runtime panics
  875. // if len(results) == 0 { return results, errors.New("Result not found.") }
  876. return results, nil
  877. }
  878. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  879. func getLoans(db *sql.DB, e int, id int) ( []Loan, error ) {
  880. var loans []Loan
  881. var query string
  882. var rows *sql.Rows
  883. var err error
  884. query = `SELECT
  885. l.id,
  886. l.type_id,
  887. l.estimate_id,
  888. l.amount,
  889. l.term,
  890. l.interest,
  891. l.ltv,
  892. l.dti,
  893. l.hoi,
  894. l.tax,
  895. l.name
  896. FROM loan l WHERE l.id = CASE @e := ? WHEN 0 THEN l.id ELSE @e END AND
  897. l.estimate_id = ?
  898. `
  899. rows, err = db.Query(query, id, e)
  900. if err != nil {
  901. return loans, err
  902. }
  903. defer rows.Close()
  904. for rows.Next() {
  905. var loan Loan
  906. if err := rows.Scan(
  907. &loan.Id,
  908. &loan.Type.Id,
  909. &loan.EstimateId,
  910. &loan.Amount,
  911. &loan.Term,
  912. &loan.Interest,
  913. &loan.Ltv,
  914. &loan.Dti,
  915. &loan.Hoi,
  916. &loan.Tax,
  917. &loan.Name,
  918. )
  919. err != nil {
  920. return loans, err
  921. }
  922. mi, err := getMi(db, loan.Id)
  923. if err != nil {
  924. return loans, err
  925. }
  926. loan.Mi = mi
  927. fees, err := getFees(db, loan.Id)
  928. if err != nil {
  929. return loans, err
  930. }
  931. loan.Fees = fees
  932. loan.Type, err = getLoanType(db, loan.Type.Id)
  933. if err != nil {
  934. return loans, err
  935. }
  936. loans = append(loans, loan)
  937. }
  938. // Prevents runtime panics
  939. if len(loans) == 0 { return loans, errors.New("Loan not found.") }
  940. return loans, nil
  941. }
  942. func getEstimates(db *sql.DB, id int, user int) ( []Estimate, error ) {
  943. var estimates []Estimate
  944. var query string
  945. var rows *sql.Rows
  946. var err error
  947. query = `SELECT
  948. id,
  949. user_id,
  950. borrower_id,
  951. transaction,
  952. price,
  953. property,
  954. occupancy,
  955. zip,
  956. pud
  957. FROM estimate WHERE id = CASE @e := ? WHEN 0 THEN id ELSE @e END AND
  958. user_id = CASE @e := ? WHEN 0 THEN user_id ELSE @e END
  959. `
  960. rows, err = db.Query(query, id, user)
  961. if err != nil {
  962. return estimates, err
  963. }
  964. defer rows.Close()
  965. for rows.Next() {
  966. var estimate Estimate
  967. if err := rows.Scan(
  968. &estimate.Id,
  969. &estimate.User,
  970. &estimate.Borrower.Id,
  971. &estimate.Transaction,
  972. &estimate.Price,
  973. &estimate.Property,
  974. &estimate.Occupancy,
  975. &estimate.Zip,
  976. &estimate.Pud,
  977. )
  978. err != nil {
  979. return estimates, err
  980. }
  981. borrower, err := getBorrower(db, estimate.Borrower.Id)
  982. if err != nil {
  983. return estimates, err
  984. }
  985. estimate.Borrower = borrower
  986. estimate.Results, err = getResults(db, estimate.Id, 0)
  987. if err != nil {
  988. return estimates, err
  989. }
  990. estimates = append(estimates, estimate)
  991. }
  992. // Prevents runtime panics
  993. if len(estimates) == 0 { return estimates, errors.New("Estimate not found.") }
  994. for i := range estimates {
  995. estimates[i].Loans, err = getLoans(db, estimates[i].Id, 0)
  996. if err != nil { return estimates, err }
  997. }
  998. return estimates, nil
  999. }
  1000. // Accepts a borrower struct and returns the id of the inserted borrower and
  1001. // any related error.
  1002. func insertBorrower(db *sql.DB, borrower Borrower) (int, error) {
  1003. var query string
  1004. var row *sql.Row
  1005. var err error
  1006. var id int // Inserted loan's id
  1007. query = `INSERT INTO borrower
  1008. (
  1009. credit_score,
  1010. monthly_income,
  1011. num
  1012. )
  1013. VALUES (?, ?, ?)
  1014. RETURNING id
  1015. `
  1016. row = db.QueryRow(query,
  1017. borrower.Credit,
  1018. borrower.Income,
  1019. borrower.Num,
  1020. )
  1021. err = row.Scan(&id)
  1022. if err != nil { return 0, err }
  1023. return id, nil
  1024. }
  1025. func insertMi(db *sql.DB, mi MI) (int, error) {
  1026. var id int
  1027. query := `INSERT INTO mi
  1028. (
  1029. type,
  1030. label,
  1031. lender,
  1032. rate,
  1033. premium,
  1034. upfront,
  1035. five_year_total,
  1036. initial_premium,
  1037. initial_rate,
  1038. initial_amount
  1039. )
  1040. VALUES (?, ?, ?, ?, ?, ?, ?)
  1041. RETURNING id`
  1042. row := db.QueryRow(query,
  1043. &mi.Type,
  1044. &mi.Label,
  1045. &mi.Lender,
  1046. &mi.Rate,
  1047. &mi.Premium,
  1048. &mi.Upfront,
  1049. &mi.FiveYearTotal,
  1050. &mi.InitialAllInPremium,
  1051. &mi.InitialAllInRate,
  1052. &mi.InitialAmount,
  1053. )
  1054. err := row.Scan(&id)
  1055. if err != nil { return 0, err }
  1056. return id, nil
  1057. }
  1058. func insertFee(db *sql.DB, fee Fee) (int, error) {
  1059. var id int
  1060. query := `INSERT INTO fee
  1061. (loan_id, amount, perc, type, notes, name, category)
  1062. VALUES (?, ?, ?, ?, ?, ?, ?)
  1063. RETURNING id`
  1064. row := db.QueryRow(query,
  1065. fee.LoanId,
  1066. fee.Amount,
  1067. fee.Perc,
  1068. fee.Type,
  1069. fee.Notes,
  1070. fee.Name,
  1071. fee.Category,
  1072. )
  1073. err := row.Scan(&id)
  1074. if err != nil { return 0, err }
  1075. return id, nil
  1076. }
  1077. func insertLoan(db *sql.DB, loan Loan) (Loan, error){
  1078. var query string
  1079. var row *sql.Row
  1080. var err error
  1081. var id int // Inserted loan's id
  1082. query = `INSERT INTO loan
  1083. (
  1084. estimate_id,
  1085. type_id,
  1086. amount,
  1087. term,
  1088. interest,
  1089. ltv,
  1090. dti,
  1091. hoi,
  1092. tax,
  1093. name
  1094. )
  1095. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1096. RETURNING id
  1097. `
  1098. row = db.QueryRow(query,
  1099. loan.EstimateId,
  1100. loan.Type.Id,
  1101. loan.Amount,
  1102. loan.Term,
  1103. loan.Interest,
  1104. loan.Ltv,
  1105. loan.Dti,
  1106. loan.Hoi,
  1107. loan.Tax,
  1108. loan.Name,
  1109. )
  1110. err = row.Scan(&id)
  1111. if err != nil { return loan, err }
  1112. _, err = insertMi(db, loan.Mi)
  1113. if err != nil { return loan, err }
  1114. for i := range loan.Fees {
  1115. _, err := insertFee(db, loan.Fees[i])
  1116. if err != nil { return loan, err }
  1117. }
  1118. loans, err := getLoans(db, id, 0)
  1119. if err != nil { return Loan{}, err }
  1120. return loans[0], nil
  1121. }
  1122. func insertEstimate(db *sql.DB, estimate Estimate) (Estimate, error){
  1123. var query string
  1124. var row *sql.Row
  1125. var err error
  1126. // var id int // Inserted estimate's id
  1127. estimate.Borrower.Id, err = insertBorrower(db, estimate.Borrower)
  1128. if err != nil { return Estimate{}, err }
  1129. query = `INSERT INTO estimate
  1130. (
  1131. user_id,
  1132. borrower_id,
  1133. transaction,
  1134. price,
  1135. property,
  1136. occupancy,
  1137. zip,
  1138. pud
  1139. )
  1140. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  1141. RETURNING id
  1142. `
  1143. row = db.QueryRow(query,
  1144. estimate.User,
  1145. estimate.Borrower.Id,
  1146. estimate.Transaction,
  1147. estimate.Price,
  1148. estimate.Property,
  1149. estimate.Occupancy,
  1150. estimate.Zip,
  1151. estimate.Pud,
  1152. )
  1153. err = row.Scan(&estimate.Id)
  1154. if err != nil { return Estimate{}, err }
  1155. for _, l := range estimate.Loans {
  1156. l.EstimateId = estimate.Id
  1157. _, err = insertLoan(db, l)
  1158. if err != nil { return estimate, err }
  1159. }
  1160. estimates, err := getEstimates(db, estimate.Id, 0)
  1161. if err != nil { return Estimate{}, err }
  1162. return estimates[0], nil
  1163. }
  1164. func createEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1165. var estimate Estimate
  1166. err := json.NewDecoder(r.Body).Decode(&estimate)
  1167. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  1168. claims, err := getClaims(r)
  1169. estimate.User = claims.Id
  1170. estimate, err = insertEstimate(db, estimate)
  1171. if err != nil { http.Error(w, err.Error(), 422); return }
  1172. estimate.Results = makeResults(estimate)
  1173. err = insertResults(db, estimate.Results)
  1174. if err != nil { http.Error(w, err.Error(), 500); return }
  1175. e, err := getEstimates(db, estimate.Id, 0)
  1176. if err != nil { http.Error(w, err.Error(), 500); return }
  1177. json.NewEncoder(w).Encode(e[0])
  1178. }
  1179. // Query all estimates that belong to the current user
  1180. func fetchEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1181. var estimates []Estimate
  1182. claims, err := getClaims(r)
  1183. estimates, err = getEstimates(db, 0, claims.Id)
  1184. if err != nil { http.Error(w, err.Error(), 500); return }
  1185. json.NewEncoder(w).Encode(estimates)
  1186. }
  1187. func checkConventional(l Loan, b Borrower) error {
  1188. if b.Credit < 620 {
  1189. return errors.New("Credit score too low for conventional loan")
  1190. }
  1191. // Buyer needs to put down 20% to avoid mortgage insurance
  1192. if (l.Ltv > 80 && l.Mi.Rate == 0) {
  1193. return errors.New(fmt.Sprintln(
  1194. l.Name,
  1195. "down payment must be 20% to avoid insurance",
  1196. ))
  1197. }
  1198. return nil
  1199. }
  1200. func checkFHA(l Loan, b Borrower) error {
  1201. if b.Credit < 500 {
  1202. return errors.New("Credit score too low for FHA loan")
  1203. }
  1204. if (l.Ltv > 96.5) {
  1205. return errors.New("FHA down payment must be at least 3.5%")
  1206. }
  1207. if (b.Credit < 580 && l.Ltv > 90) {
  1208. return errors.New("FHA down payment must be at least 10%")
  1209. }
  1210. // Debt-to-income must be below 45% if credit score is below 580.
  1211. if (b.Credit < 580 && l.Dti > 45) {
  1212. return errors.New(fmt.Sprintln(
  1213. l.Name, "debt to income is too high for credit score.",
  1214. ))
  1215. }
  1216. return nil
  1217. }
  1218. // Loan option for veterans with no set rules
  1219. func checkVA(l Loan, b Borrower) error {
  1220. return nil
  1221. }
  1222. // Loan option for residents of rural areas with no set rules
  1223. func checkUSDA(l Loan, b Borrower) error {
  1224. return nil
  1225. }
  1226. // Should also check loan amount limit maybe with an API.
  1227. func checkEstimate(e Estimate) error {
  1228. if e.Property == "" { return errors.New("Empty property type") }
  1229. if e.Price == 0 { return errors.New("Empty property price") }
  1230. if e.Borrower.Num == 0 {
  1231. return errors.New("Missing number of borrowers")
  1232. }
  1233. if e.Borrower.Credit == 0 {
  1234. return errors.New("Missing borrower credit score")
  1235. }
  1236. if e.Borrower.Income == 0 {
  1237. return errors.New("Missing borrower credit income")
  1238. }
  1239. for _, l := range e.Loans {
  1240. if l.Amount == 0 {
  1241. return errors.New(fmt.Sprintln(l.Name, "loan amount cannot be zero"))
  1242. }
  1243. if l.Term == 0 {
  1244. return errors.New(fmt.Sprintln(l.Name, "loan term cannot be zero"))
  1245. }
  1246. if l.Interest == 0 {
  1247. return errors.New(fmt.Sprintln(l.Name, "loan interest cannot be zero"))
  1248. }
  1249. // Can be used to check rules for specific loan types
  1250. var err error
  1251. switch l.Type.Id {
  1252. case 1:
  1253. err = checkConventional(l, e.Borrower)
  1254. case 2:
  1255. err = checkFHA(l, e.Borrower)
  1256. case 3:
  1257. err = checkVA(l, e.Borrower)
  1258. case 4:
  1259. err = checkUSDA(l, e.Borrower)
  1260. default:
  1261. err = errors.New("Invalid loan type")
  1262. }
  1263. if err != nil { return err }
  1264. }
  1265. return nil
  1266. }
  1267. func validateEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1268. var estimate Estimate
  1269. err := json.NewDecoder(r.Body).Decode(&estimate)
  1270. if err != nil { http.Error(w, err.Error(), 422); return }
  1271. err = checkEstimate(estimate)
  1272. if err != nil { http.Error(w, err.Error(), 406); return }
  1273. }
  1274. func showPDF(w http.ResponseWriter, r *http.Request) {
  1275. // var args []string
  1276. // p := r.URL.Path
  1277. db, err := sql.Open("mysql",
  1278. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
  1279. config["DBUser"],
  1280. config["DBPass"]))
  1281. // w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  1282. err = db.Ping()
  1283. if err != nil {
  1284. fmt.Println("Bad database configuration: %v\n", err)
  1285. panic(err)
  1286. // maybe os.Exit(1) instead
  1287. }
  1288. var pa = template.Must(template.ParseFiles("views/master.tpl",
  1289. "views/test.tpl"))
  1290. // claims, err := getClaims(r)
  1291. if err != nil { w.WriteHeader(500); return }
  1292. users, err := queryUsers(db, 1)
  1293. info := struct {
  1294. Title string
  1295. Name string
  1296. User User
  1297. }{
  1298. Title: "test PDF",
  1299. Name: "idk-random-name",
  1300. User: users[0],
  1301. }
  1302. // fmt.Println(info)
  1303. err = pa.Execute(w, info)
  1304. if err != nil {fmt.Println(err)}
  1305. }
  1306. func api(w http.ResponseWriter, r *http.Request) {
  1307. var args []string
  1308. p := r.URL.Path
  1309. db, err := sql.Open("mysql",
  1310. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
  1311. config["DBUser"],
  1312. config["DBPass"]))
  1313. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  1314. err = db.Ping()
  1315. if err != nil {
  1316. fmt.Println("Bad database configuration: %v\n", err)
  1317. panic(err)
  1318. // maybe os.Exit(1) instead
  1319. }
  1320. switch {
  1321. case match(p, "/api/login", &args) &&
  1322. r.Method == http.MethodPost:
  1323. login(w, db, r)
  1324. case match(p, "/api/token", &args) &&
  1325. r.Method == http.MethodGet && guard(r, 1):
  1326. getToken(w, db, r)
  1327. case match(p, "/api/users", &args) && // Array of all users
  1328. r.Method == http.MethodGet && guard(r, 3):
  1329. getUsers(w, db, r)
  1330. case match(p, "/api/user", &args) &&
  1331. r.Method == http.MethodGet && guard(r, 1):
  1332. getUser(w, db, r)
  1333. case match(p, "/api/user", &args) &&
  1334. r.Method == http.MethodPost &&
  1335. guard(r, 3):
  1336. createUser(w, db, r)
  1337. case match(p, "/api/user", &args) &&
  1338. r.Method == http.MethodPatch &&
  1339. guard(r, 3): // For admin to modify any user
  1340. patchUser(w, db, r)
  1341. case match(p, "/api/user", &args) &&
  1342. r.Method == http.MethodPatch &&
  1343. guard(r, 2): // For employees to modify own accounts
  1344. patchSelf(w, db, r)
  1345. case match(p, "/api/user", &args) &&
  1346. r.Method == http.MethodDelete &&
  1347. guard(r, 3):
  1348. deleteUser(w, db, r)
  1349. case match(p, "/api/fees", &args) &&
  1350. r.Method == http.MethodGet &&
  1351. guard(r, 1):
  1352. getFeesTemp(w, db, r)
  1353. case match(p, "/api/fee", &args) &&
  1354. r.Method == http.MethodPost &&
  1355. guard(r, 1):
  1356. createFeesTemp(w, db, r)
  1357. case match(p, "/api/fee", &args) &&
  1358. r.Method == http.MethodDelete &&
  1359. guard(r, 1):
  1360. deleteFeeTemp(w, db, r)
  1361. case match(p, "/api/estimates", &args) &&
  1362. r.Method == http.MethodGet &&
  1363. guard(r, 1):
  1364. fetchEstimate(w, db, r)
  1365. case match(p, "/api/estimate", &args) &&
  1366. r.Method == http.MethodPost &&
  1367. guard(r, 1):
  1368. createEstimate(w, db, r)
  1369. case match(p, "/api/estimate/validate", &args) &&
  1370. r.Method == http.MethodPost &&
  1371. guard(r, 1):
  1372. validateEstimate(w, db, r)
  1373. case match(p, "/api/estimate/summarize", &args) &&
  1374. r.Method == http.MethodPost &&
  1375. guard(r, 1):
  1376. summarize(w, db, r)
  1377. default:
  1378. http.Error(w, "Invalid route or token", 404)
  1379. }
  1380. db.Close()
  1381. }
  1382. func route(w http.ResponseWriter, r *http.Request) {
  1383. var page Page
  1384. var args []string
  1385. p := r.URL.Path
  1386. switch {
  1387. case r.Method == "GET" && match(p, "/", &args):
  1388. page = pages[ "home" ]
  1389. case match(p, "/terms", &args):
  1390. page = pages[ "terms" ]
  1391. case match(p, "/app", &args):
  1392. page = pages[ "app" ]
  1393. case match(p, "/test", &args):
  1394. showPDF(w, r)
  1395. return
  1396. default:
  1397. http.NotFound(w, r)
  1398. return
  1399. }
  1400. page.Render(w)
  1401. }
  1402. func main() {
  1403. files := http.FileServer(http.Dir(""))
  1404. http.Handle("/assets/", files)
  1405. http.HandleFunc("/api/", api)
  1406. http.HandleFunc("/", route)
  1407. log.Fatal(http.ListenAndServe(address, nil))
  1408. }