Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2748 lines
58 KiB

  1. package main
  2. import (
  3. "os"
  4. "os/exec"
  5. "net/http"
  6. "net/mail"
  7. "log"
  8. "sync"
  9. "regexp"
  10. "html/template"
  11. "database/sql"
  12. _ "github.com/go-sql-driver/mysql"
  13. "fmt"
  14. "encoding/json"
  15. "encoding/base64"
  16. "strconv"
  17. "bytes"
  18. "time"
  19. "errors"
  20. "strings"
  21. "math"
  22. "io"
  23. // pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
  24. "github.com/golang-jwt/jwt/v4"
  25. "github.com/disintegration/gift"
  26. "github.com/brianvoe/gofakeit/v6"
  27. "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. }