Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

2118 wiersze
45 KiB

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