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

2124 lines
45 KiB

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