Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 
 
 

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