Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

1932 Zeilen
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. 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. func queryUsers(db *sql.DB, id int) ( []User, error ) {
  629. var users []User
  630. var query string
  631. var rows *sql.Rows
  632. var err error
  633. query = `SELECT
  634. u.id,
  635. u.email,
  636. u.first_name,
  637. u.last_name,
  638. u.branch_id,
  639. u.country,
  640. u.title,
  641. u.status,
  642. u.verified,
  643. u.role
  644. FROM user u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
  645. `
  646. rows, err = db.Query(query, id)
  647. if err != nil {
  648. return users, err
  649. }
  650. defer rows.Close()
  651. for rows.Next() {
  652. var user User
  653. if err := rows.Scan(
  654. &user.Id,
  655. &user.Email,
  656. &user.FirstName,
  657. &user.LastName,
  658. &user.BranchId,
  659. &user.Country,
  660. &user.Title,
  661. &user.Status,
  662. &user.Verified,
  663. &user.Role,
  664. )
  665. err != nil {
  666. return users, err
  667. }
  668. users = append(users, user)
  669. }
  670. // Prevents runtime panics
  671. if len(users) == 0 { return users, errors.New("User not found.") }
  672. return users, nil
  673. }
  674. func insertResults(db *sql.DB, results []Result) (error){
  675. var query string
  676. var row *sql.Row
  677. var err error
  678. query = `INSERT INTO estimate_result
  679. (
  680. loan_id,
  681. loan_payment,
  682. total_monthly,
  683. total_fees,
  684. total_credits,
  685. cash_to_close
  686. )
  687. VALUES (?, ?, ?, ?, ?, ?, ?)
  688. RETURNING id
  689. `
  690. for i := range results {
  691. db.QueryRow(query,
  692. results[i].LoanId,
  693. results[i].LoanPayment,
  694. results[i].TotalMonthly,
  695. results[i].TotalFees,
  696. results[i].TotalCredits,
  697. results[i].CashToClose,
  698. )
  699. err = row.Scan(&results[i].Id)
  700. if err != nil { return err }
  701. }
  702. return nil
  703. }
  704. func insertUser(db *sql.DB, user User) (User, error){
  705. var query string
  706. var row *sql.Row
  707. var err error
  708. var id int // Inserted user's id
  709. query = `INSERT INTO user
  710. (
  711. email,
  712. first_name,
  713. last_name,
  714. password,
  715. created,
  716. role,
  717. verified,
  718. last_login
  719. )
  720. VALUES (?, ?, ?, sha2(?, 256), NOW(), ?, ?, NOW())
  721. RETURNING id
  722. `
  723. row = db.QueryRow(query,
  724. user.Email,
  725. user.FirstName,
  726. user.LastName,
  727. user.Password,
  728. user.Role,
  729. user.Verified,
  730. )
  731. err = row.Scan(&id)
  732. if err != nil { return User{}, err }
  733. users, err := queryUsers(db, id)
  734. if err != nil { return User{}, err }
  735. return users[0], nil
  736. }
  737. func updateUser(user User, db *sql.DB) error {
  738. query := `
  739. UPDATE user
  740. SET
  741. email = CASE @e := ? WHEN '' THEN email ELSE @e END,
  742. first_name = CASE @fn := ? WHEN '' THEN first_name ELSE @fn END,
  743. last_name = CASE @ln := ? WHEN '' THEN last_name ELSE @ln END,
  744. role = CASE @r := ? WHEN '' THEN role ELSE @r END,
  745. password = CASE @p := ? WHEN '' THEN password ELSE sha2(@p, 256) END
  746. WHERE id = ?
  747. `
  748. _, err := db.Exec(query,
  749. user.Email,
  750. user.FirstName,
  751. user.LastName,
  752. user.Role,
  753. user.Password,
  754. user.Id,
  755. )
  756. return err
  757. }
  758. func getUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  759. claims, err := getClaims(r)
  760. if err != nil { w.WriteHeader(500); return }
  761. users, err := queryUsers(db, claims.Id)
  762. if err != nil { w.WriteHeader(422); log.Println(err); return }
  763. json.NewEncoder(w).Encode(users)
  764. }
  765. func getUsers(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  766. users, err := queryUsers(db, 0)
  767. if err != nil {
  768. w.WriteHeader(http.StatusInternalServerError)
  769. return
  770. }
  771. json.NewEncoder(w).Encode(users)
  772. }
  773. // Updates a user using only specified values in the JSON body
  774. func setUser(user User, db *sql.DB) error {
  775. _, err := mail.ParseAddress(user.Email)
  776. if err != nil { return err }
  777. if roles[user.Role] == 0 {
  778. return errors.New("Invalid role")
  779. }
  780. err = updateUser(user, db)
  781. if err != nil { return err }
  782. return nil
  783. }
  784. func patchUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  785. var user User
  786. err := json.NewDecoder(r.Body).Decode(&user)
  787. if err != nil { http.Error(w, "Invalid fields", 422); return }
  788. err = setUser(user, db)
  789. if err != nil { http.Error(w, err.Error(), 422); return }
  790. }
  791. // Update specified fields of the user specified in the claim
  792. func patchSelf(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  793. claim, err := getClaims(r)
  794. var user User
  795. json.NewDecoder(r.Body).Decode(&user)
  796. // First check that the target user to be updated is the same as the claim id, and
  797. // their role is unchanged.
  798. if err != nil || claim.Id != user.Id {
  799. http.Error(w, "Target user's id does not match claim.", 401)
  800. return
  801. }
  802. if claim.Role != user.Role && user.Role != "" {
  803. http.Error(w, "Administrator required to escalate role.", 401)
  804. return
  805. }
  806. err = setUser(user, db)
  807. if err != nil { http.Error(w, err.Error(), 422); return }
  808. }
  809. func deleteUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  810. var user User
  811. err := json.NewDecoder(r.Body).Decode(&user)
  812. if err != nil {
  813. http.Error(w, "Invalid fields.", 422)
  814. return
  815. }
  816. query := `DELETE FROM user WHERE id = ?`
  817. _, err = db.Exec(query, user.Id)
  818. if err != nil {
  819. http.Error(w, "Could not delete.", 500)
  820. }
  821. }
  822. func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  823. var user User
  824. err := json.NewDecoder(r.Body).Decode(&user)
  825. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  826. _, err = mail.ParseAddress(user.Email)
  827. if err != nil { http.Error(w, "Invalid email.", 422); return }
  828. if roles[user.Role] == 0 {
  829. http.Error(w, "Invalid role.", 422)
  830. }
  831. user, err = insertUser(db, user)
  832. if err != nil { http.Error(w, "Error creating user.", 422); return }
  833. json.NewEncoder(w).Encode(user)
  834. }
  835. func checkPassword(db *sql.DB, id int, pass string) bool {
  836. var p string
  837. query := `SELECT
  838. password
  839. FROM user WHERE user.id = ? AND password = sha2(?, 256)
  840. `
  841. row := db.QueryRow(query, id, pass)
  842. err := row.Scan(&p)
  843. if err != nil { return false }
  844. return true
  845. }
  846. func setPassword(db *sql.DB, id int, pass string) error {
  847. query := `UPDATE user
  848. SET password = sha2(?, 256)
  849. WHERE user.id = ?
  850. `
  851. _, err := db.Exec(query, pass, id)
  852. if err != nil { return errors.New("Could not insert password.") }
  853. return nil
  854. }
  855. func changePassword(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  856. var pass Password
  857. claim, err := getClaims(r)
  858. err = json.NewDecoder(r.Body).Decode(&pass)
  859. if err != nil { http.Error(w, "Bad fields.", 422); return }
  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. }