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.
 
 
 
 
 
 

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