Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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