Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 
 

2193 строки
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. }