Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 
 
 

2744 líneas
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. default:
  2033. http.NotFound(w, r)
  2034. return
  2035. }
  2036. page.Render(w)
  2037. }
  2038. func serve() {
  2039. files := http.FileServer(http.Dir(""))
  2040. http.Handle("/assets/", files)
  2041. http.HandleFunc("/api/", api)
  2042. http.HandleFunc("/", route)
  2043. log.Fatal(http.ListenAndServe(address, nil))
  2044. }
  2045. func dbReset(db *sql.DB) {
  2046. b, err := os.ReadFile("migrations/reset.sql")
  2047. if err != nil {
  2048. log.Fatal(err)
  2049. }
  2050. _, err = db.Exec(string(b))
  2051. if err != nil {
  2052. log.Fatal(err)
  2053. }
  2054. b, err = os.ReadFile("migrations/0_29092022_setup_tables.sql")
  2055. if err != nil {
  2056. log.Fatal(err)
  2057. }
  2058. _, err = db.Exec(string(b))
  2059. if err != nil {
  2060. log.Fatal(err)
  2061. }
  2062. }
  2063. func generateFees(loan Loan) []Fee {
  2064. var fees []Fee
  2065. var fee Fee
  2066. p := gofakeit.Float32Range(0.5, 10)
  2067. size := gofakeit.Number(1, 10)
  2068. for f := 0; f < size; f++ {
  2069. fee = Fee{
  2070. Amount: int(float32(loan.Amount)*p/100),
  2071. Perc: p,
  2072. Name: gofakeit.BuzzWord(),
  2073. Type: feeTypes[gofakeit.Number(0, len(feeTypes) - 1)],
  2074. }
  2075. fees = append(fees, fee)
  2076. }
  2077. return fees
  2078. }
  2079. func generateCredits(loan Loan) []Fee {
  2080. var fees []Fee
  2081. var fee Fee
  2082. p := gofakeit.Float32Range(-10, -0.5)
  2083. size := gofakeit.Number(1, 10)
  2084. for f := 0; f < size; f++ {
  2085. fee = Fee{
  2086. Amount: int(float32(loan.Amount)*p/100),
  2087. Perc: p,
  2088. Name: gofakeit.BuzzWord(),
  2089. Type: feeTypes[gofakeit.Number(0, len(feeTypes) - 1)],
  2090. }
  2091. fees = append(fees, fee)
  2092. }
  2093. return fees
  2094. }
  2095. func seedAddresses(db *sql.DB) []Address {
  2096. addresses := make([]Address, 10)
  2097. for i, a := range addresses {
  2098. a.Street = gofakeit.Street()
  2099. a.City = gofakeit.City()
  2100. a.Region = gofakeit.State()
  2101. a.Country = "Canada"
  2102. a.Full = fmt.Sprintf("%s, %s %s", a.Street, a.City, a.Region)
  2103. id, err := insertAddress(db, a)
  2104. if err != nil {log.Println(err); break}
  2105. addresses[i].Id = id
  2106. }
  2107. return addresses
  2108. }
  2109. func seedBranches(db *sql.DB, addresses []Address) []Branch {
  2110. branches := make([]Branch, 4)
  2111. for i := range branches {
  2112. branches[i].Name = gofakeit.Company()
  2113. branches[i].Type = "NMLS"
  2114. branches[i].Letterhead = gofakeit.ImagePng(400, 200)
  2115. branches[i].Num = gofakeit.HexUint8()
  2116. branches[i].Phone = gofakeit.Phone()
  2117. branches[i].Address.Id = gofakeit.Number(1, 5)
  2118. id, err := insertBranch(db, branches[i])
  2119. if err != nil {log.Println(err); break}
  2120. branches[i].Id = id
  2121. }
  2122. return branches
  2123. }
  2124. func seedUsers(db *sql.DB, addresses []Address, branches []Branch) []User {
  2125. users := make([]User, 10)
  2126. for i := range users {
  2127. p := gofakeit.Person()
  2128. users[i].FirstName = p.FirstName
  2129. users[i].LastName = p.LastName
  2130. users[i].Email = p.Contact.Email
  2131. users[i].Phone = p.Contact.Phone
  2132. users[i].Branch = branches[gofakeit.Number(0, 3)]
  2133. users[i].Address = addresses[gofakeit.Number(1, 9)]
  2134. // users[i].Letterhead = gofakeit.ImagePng(400, 200)
  2135. // users[i].Avatar = gofakeit.ImagePng(200, 200)
  2136. users[i].Country = []string{"Canada", "USA"}[gofakeit.Number(0, 1)]
  2137. users[i].Password = "test123"
  2138. users[i].Verified = true
  2139. users[i].Title = "Loan Officer"
  2140. users[i].Status = "Subscribed"
  2141. users[i].Role = "User"
  2142. }
  2143. users[0].Email = "test@example.com"
  2144. users[0].Email = "test@example.com"
  2145. users[1].Email = "test2@example.com"
  2146. users[1].Status = "Branch"
  2147. users[1].Role = "Manager"
  2148. users[2].Email = "test3@example.com"
  2149. users[2].Status = "Free"
  2150. users[2].Role = "Admin"
  2151. for i := range users {
  2152. u, err := insertUser(db, users[i])
  2153. if err != nil {log.Println(err); break}
  2154. users[i].Id = u.Id
  2155. }
  2156. return users
  2157. }
  2158. func seedLicenses(db *sql.DB, users []User) []License {
  2159. licenses := make([]License, len(users))
  2160. for i := range licenses {
  2161. licenses[i].UserId = users[i].Id
  2162. licenses[i].Type = []string{"NMLS", "FSRA"}[gofakeit.Number(0, 1)]
  2163. licenses[i].Num = gofakeit.UUID()
  2164. id, err := insertLicense(db, licenses[i])
  2165. if err != nil {log.Println(err); break}
  2166. licenses[i].Id = id
  2167. }
  2168. return licenses
  2169. }
  2170. func seedLoanTypes(db *sql.DB) []LoanType {
  2171. var loantypes []LoanType
  2172. var loantype LoanType
  2173. var err error
  2174. loantype = LoanType{Branch: 0, User: 0, Name: "Conventional"}
  2175. loantype.Id, err = insertLoanType(db, loantype)
  2176. if err != nil { panic(err) }
  2177. loantypes = append(loantypes, loantype)
  2178. loantype = LoanType{Branch: 0, User: 0, Name: "FHA"}
  2179. loantype.Id, err = insertLoanType(db, loantype)
  2180. if err != nil { panic(err) }
  2181. loantypes = append(loantypes, loantype)
  2182. loantype = LoanType{Branch: 0, User: 0, Name: "USDA"}
  2183. loantype.Id, err = insertLoanType(db, loantype)
  2184. if err != nil { panic(err) }
  2185. loantypes = append(loantypes, loantype)
  2186. loantype = LoanType{Branch: 0, User: 0, Name: "VA"}
  2187. loantype.Id, err = insertLoanType(db, loantype)
  2188. if err != nil { panic(err) }
  2189. loantypes = append(loantypes, loantype)
  2190. return loantypes
  2191. }
  2192. func seedEstimates(db *sql.DB, users []User, ltypes []LoanType) []Estimate {
  2193. var estimates []Estimate
  2194. var estimate Estimate
  2195. var l Loan
  2196. var err error
  2197. for i := 0; i < 15; i++ {
  2198. estimate = Estimate{}
  2199. estimate.User = users[gofakeit.Number(0, len(users) - 1)].Id
  2200. estimate.Borrower = Borrower{
  2201. Credit: gofakeit.Number(600, 800),
  2202. Income: gofakeit.Number(1000000, 15000000),
  2203. Num: gofakeit.Number(1, 20),
  2204. }
  2205. estimate.Transaction = []string{"Purchase", "Refinance"}[gofakeit.Number(0, 1)]
  2206. estimate.Price = gofakeit.Number(50000, 200000000)
  2207. estimate.Property =
  2208. propertyTypes[gofakeit.Number(0, len(propertyTypes) - 1)]
  2209. estimate.Occupancy =
  2210. []string{"Primary", "Secondary", "Investment"}[gofakeit.Number(0, 2)]
  2211. estimate.Zip = gofakeit.Zip()
  2212. lsize := gofakeit.Number(1, 6)
  2213. for j := 0; j < lsize; j++ {
  2214. l.Type = ltypes[gofakeit.Number(0, len(ltypes) - 1)]
  2215. l.Amount = gofakeit.Number(
  2216. int(float32(estimate.Price)*0.5),
  2217. int(float32(estimate.Price)*0.93))
  2218. l.Term = gofakeit.Number(4, 30)
  2219. l.Hoi = gofakeit.Number(50000, 700000)
  2220. l.Hazard = gofakeit.Number(5000, 200000)
  2221. l.Tax = gofakeit.Number(5000, 200000)
  2222. l.Interest = gofakeit.Float32Range(0.5, 8)
  2223. l.Fees = generateFees(l)
  2224. l.Credits = generateCredits(l)
  2225. l.Name = gofakeit.AdjectiveDescriptive()
  2226. estimate.Loans = append(estimate.Loans, l)
  2227. }
  2228. estimates = append(estimates, estimate)
  2229. }
  2230. estimate[0].User = users[0].Id
  2231. estimate[1].User = users[0].Id
  2232. for i := range estimates {
  2233. err = estimates[i].insertEstimate(db)
  2234. if err != nil {log.Println(err); return estimates}
  2235. }
  2236. return estimates
  2237. }
  2238. func seedResults(db *sql.DB, estimates []Estimate) error {
  2239. var err error
  2240. for i := range estimates {
  2241. estimates[i].makeResults()
  2242. err = estimates[i].insertResults(db)
  2243. if err != nil {log.Println(err); return err}
  2244. }
  2245. return nil
  2246. }
  2247. func dbSeed(db *sql.DB) {
  2248. addresses := seedAddresses(db)
  2249. branches := seedBranches(db, addresses)
  2250. users := seedUsers(db, addresses, branches)
  2251. _ = seedLicenses(db, users)
  2252. loantypes := seedLoanTypes(db)
  2253. estimates := seedEstimates(db, users, loantypes)
  2254. _ = seedResults(db, estimates)
  2255. }
  2256. func dev(args []string) {
  2257. os.Setenv("DBName", "skouter_dev")
  2258. os.Setenv("DBUser", "tester")
  2259. os.Setenv("DBPass", "test123")
  2260. db, err := sql.Open("mysql",
  2261. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s?multiStatements=true",
  2262. os.Getenv("DBUser"),
  2263. os.Getenv("DBPass"),
  2264. os.Getenv("DBName"),
  2265. ))
  2266. err = db.Ping()
  2267. if err != nil {
  2268. log.Println("Bad database configuration: %v", err)
  2269. panic(err)
  2270. // maybe os.Exit(1) instead
  2271. }
  2272. if len(args) == 0 {
  2273. serve()
  2274. return
  2275. }
  2276. switch args[0] {
  2277. case "seed":
  2278. dbSeed(db)
  2279. case "reset":
  2280. dbReset(db)
  2281. default:
  2282. return
  2283. }
  2284. db.Close()
  2285. }
  2286. func check(args []string) {
  2287. os.Setenv("DBName", "skouter_dev")
  2288. os.Setenv("DBUser", "tester")
  2289. os.Setenv("DBPass", "test123")
  2290. files := http.FileServer(http.Dir(""))
  2291. http.Handle("/assets/", files)
  2292. http.HandleFunc("/", checkPdf)
  2293. log.Fatal(http.ListenAndServe(address, nil))
  2294. }
  2295. func main() {
  2296. if len(os.Args) <= 1 {
  2297. serve()
  2298. return
  2299. }
  2300. switch os.Args[1] {
  2301. case "dev":
  2302. dev(os.Args[2:])
  2303. case "checkpdf":
  2304. check(os.Args[2:])
  2305. default:
  2306. return
  2307. }
  2308. }