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.
 
 
 
 
 
 

2157 line
46 KiB

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