Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

1846 行
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. }