Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

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