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.
 
 
 
 
 
 

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