Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

2681 linhas
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. }