Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

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