Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 
 
 

2026 satır
44 KiB

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