Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 
 
 

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