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.
 
 
 
 
 
 

1899 lines
41 KiB

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