Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2026 lines
44 KiB

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