Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1809 lines
39 KiB

  1. package main
  2. import (
  3. "net/http"
  4. "net/mail"
  5. "log"
  6. "sync"
  7. "regexp"
  8. "html/template"
  9. "database/sql"
  10. _ "github.com/go-sql-driver/mysql"
  11. "fmt"
  12. "encoding/json"
  13. "strconv"
  14. "bytes"
  15. "time"
  16. "errors"
  17. "strings"
  18. "math"
  19. "io"
  20. // pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
  21. "github.com/golang-jwt/jwt/v4"
  22. )
  23. type User struct {
  24. Id int `json:"id"`
  25. Email string `json:"email"`
  26. FirstName string `json:"firstName"`
  27. LastName string `json:"lastName"`
  28. BranchId int `json:"branchId"`
  29. Status string `json:"status"`
  30. Country string `json:"country"`
  31. Title string `json:"title"`
  32. Verified bool `json:"verified"`
  33. Role string `json:"role"`
  34. Password string `json:"password,omitempty"`
  35. }
  36. type UserClaims struct {
  37. Id int `json:"id"`
  38. Role string `json:"role"`
  39. Exp string `json:"exp"`
  40. }
  41. type Page struct {
  42. tpl *template.Template
  43. Title string
  44. Name string
  45. }
  46. type Borrower struct {
  47. Id int `json:"id"`
  48. Credit int `json:"credit"`
  49. Income int `json:"income"`
  50. Num int `json:"num"`
  51. }
  52. type FeeTemplate struct {
  53. Id int `json:"id"`
  54. User int `json:"user"`
  55. Branch int `json:"branch"`
  56. Amount int `json:"amount"`
  57. Perc float32 `json:"perc"`
  58. Type string `json:"type"`
  59. Notes string `json:"notes"`
  60. Name string `json:"name"`
  61. Category string `json:"category"`
  62. Auto bool `json:"auto"`
  63. }
  64. type Fee struct {
  65. Id int `json:"id"`
  66. LoanId int `json:"loan_id"`
  67. Amount int `json:"amount"`
  68. Perc float32 `json:"perc"`
  69. Type string `json:"type"`
  70. Notes string `json:"notes"`
  71. Name string `json:"name"`
  72. Category string `json:"category"`
  73. }
  74. type LoanType struct {
  75. Id int `json:"id"`
  76. User int `json:"user"`
  77. Branch int `json:"branch"`
  78. Name string `json:"name"`
  79. }
  80. type Loan struct {
  81. Id int `json:"id"`
  82. EstimateId int `json:"estimateId"`
  83. Type LoanType `json:"type"`
  84. Amount int `json:"amount"`
  85. Amortization string `json:"amortization"`
  86. Term int `json:"term"`
  87. Ltv float32 `json:"ltv"`
  88. Dti float32 `json:"dti"`
  89. Hoi int `json:"hoi"`
  90. Hazard int `json:"hazard"`
  91. Tax int `json:"tax"`
  92. Interest float32 `json:"interest"`
  93. Mi MI `json:"mi"`
  94. Fees []Fee `json:"fees"`
  95. Name string `json:"title"`
  96. }
  97. type MI struct {
  98. Type string `json:"user"`
  99. Label string `json:"label"`
  100. Lender string `json:"lender"`
  101. Rate float32 `json:"rate"`
  102. Premium float32 `json:"premium"`
  103. Upfront float32 `json:"upfront"`
  104. Monthly bool `json:"monthly"`
  105. FiveYearTotal float32 `json:"fiveYearTotal"`
  106. InitialAllInPremium float32 `json:"initialAllInPremium"`
  107. InitialAllInRate float32 `json:"initialAllInRate"`
  108. InitialAmount float32 `json:"initialAmount"`
  109. }
  110. type Result struct {
  111. Id int `json:"id"`
  112. LoanId int `json:"loanId"`
  113. LoanPayment int `json:"loanPayment"`
  114. TotalMonthly int `json:"totalMonthly"`
  115. TotalFees int `json:"totalFees"`
  116. TotalCredits int `json:"totalCredits"`
  117. CashToClose int `json:"cashToClose"`
  118. }
  119. type Estimate struct {
  120. Id int `json:"id"`
  121. User int `json:"user"`
  122. Borrower Borrower `json:"borrower"`
  123. Transaction string `json:"transaction"`
  124. Price int `json:"price"`
  125. Property string `json:"property"`
  126. Occupancy string `json:"occupancy"`
  127. Zip string `json:"zip"`
  128. Pud bool `json:"pud"`
  129. Loans []Loan `json:"loans"`
  130. Results []Result `json:"results"`
  131. }
  132. var (
  133. regexen = make(map[string]*regexp.Regexp)
  134. relock sync.Mutex
  135. address = "127.0.0.1:8001"
  136. )
  137. var paths = map[string]string {
  138. "home": "views/home.tpl",
  139. "terms": "views/terms.tpl",
  140. "app": "views/app.tpl",
  141. "test": "views/test.tpl",
  142. }
  143. var pages = map[string]Page {
  144. "home": cache("home", "Home"),
  145. "terms": cache("terms", "Terms and Conditions"),
  146. "test": cache("test", "PDF test"),
  147. "app": cache("app", "App"),
  148. }
  149. var roles = map[string]int{
  150. "User": 1,
  151. "Manager": 2,
  152. "Admin": 3,
  153. }
  154. // Used to validate claim in JWT token body. Checks if user id is greater than
  155. // zero and time format is valid
  156. func (c UserClaims) Valid() error {
  157. if c.Id < 1 { return errors.New("Invalid id") }
  158. t, err := time.Parse(time.UnixDate, c.Exp)
  159. if err != nil { return err }
  160. if t.Before(time.Now()) { return errors.New("Token expired.") }
  161. return err
  162. }
  163. func cache(name string, title string) Page {
  164. var p = []string{"views/master.tpl", paths[name]}
  165. tpl := template.Must(template.ParseFiles(p...))
  166. return Page{tpl: tpl,
  167. Title: title,
  168. Name: name,
  169. }
  170. }
  171. func (page Page) Render(w http.ResponseWriter) {
  172. err := page.tpl.Execute(w, page)
  173. if err != nil {
  174. log.Print(err)
  175. }
  176. }
  177. func match(path, pattern string, args *[]string) bool {
  178. relock.Lock()
  179. defer relock.Unlock()
  180. regex := regexen[pattern]
  181. if regex == nil {
  182. regex = regexp.MustCompile("^" + pattern + "$")
  183. regexen[pattern] = regex
  184. }
  185. matches := regex.FindStringSubmatch(path)
  186. if len(matches) <= 0 {
  187. return false
  188. }
  189. *args = matches[1:]
  190. return true
  191. }
  192. func makeResults(estimate Estimate) ([]Result) {
  193. var results []Result
  194. amortize := func(principle float64, rate float64, periods float64) int {
  195. exp := math.Pow(1+rate, periods)
  196. return int(principle * rate * exp / (exp - 1))
  197. }
  198. for _, loan := range estimate.Loans {
  199. var result Result
  200. // Monthly payments use amortized loan payment formula plus monthly MI,
  201. // plus all other recurring fees
  202. result.LoanPayment = amortize(float64(loan.Amount),
  203. float64(loan.Interest / 100 / 12),
  204. float64(loan.Term * 12))
  205. result.TotalMonthly = result.LoanPayment + loan.Hoi + loan.Tax + loan.Hazard
  206. if loan.Mi.Monthly {
  207. result.TotalMonthly = result.TotalMonthly +
  208. int(loan.Mi.Rate/100*float32(loan.Amount))
  209. }
  210. for i := range loan.Fees {
  211. if loan.Fees[i].Amount > 0 {
  212. result.TotalFees = result.TotalFees + loan.Fees[i].Amount
  213. } else {
  214. result.TotalCredits = result.TotalCredits + loan.Fees[i].Amount
  215. }
  216. }
  217. result.CashToClose =
  218. result.TotalFees + result.TotalCredits + (estimate.Price - loan.Amount)
  219. result.LoanId = loan.Id
  220. results = append(results, result)
  221. }
  222. return results
  223. }
  224. func summarize(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  225. var estimate Estimate
  226. err := json.NewDecoder(r.Body).Decode(&estimate)
  227. if err != nil { http.Error(w, "Invalid estimate.", 422); return }
  228. results := makeResults(estimate)
  229. json.NewEncoder(w).Encode(results)
  230. }
  231. func getLoanType( db *sql.DB, id int) (LoanType, error) {
  232. types, err := getLoanTypes(db, id, 0, 0)
  233. if err != nil { return LoanType{Id: id}, err }
  234. if len(types) == 0 {
  235. return LoanType{Id: id}, errors.New("No type with that id")
  236. }
  237. return types[0], nil
  238. }
  239. func getLoanTypes( db *sql.DB, id int, user int, branch int ) (
  240. []LoanType, error) {
  241. var loans []LoanType
  242. var query = `SELECT
  243. id,
  244. user_id,
  245. branch_id,
  246. name
  247. FROM loan_type WHERE loan_type.id = CASE @e := ? WHEN 0 THEN id ELSE @e END
  248. OR
  249. loan_type.user_id = CASE @e := ? WHEN 0 THEN id ELSE @e END
  250. OR
  251. loan_type.branch_id = CASE @e := ? WHEN 0 THEN id ELSE @e END`
  252. // Should be changed to specify user
  253. rows, err := db.Query(query, id, user, branch)
  254. if err != nil {
  255. return nil, fmt.Errorf("loan_type error: %v", err)
  256. }
  257. defer rows.Close()
  258. for rows.Next() {
  259. var loan LoanType
  260. if err := rows.Scan(
  261. &loan.Id,
  262. &loan.User,
  263. &loan.Branch,
  264. &loan.Name)
  265. err != nil {
  266. log.Printf("Error occured fetching loan: %v", err)
  267. return nil, fmt.Errorf("Error occured fetching loan: %v", err)
  268. }
  269. loans = append(loans, loan)
  270. }
  271. return loans, nil
  272. }
  273. func getFees(db *sql.DB, loan int) ([]Fee, error) {
  274. var fees []Fee
  275. query := `SELECT id, loan_id, amount, perc, type, notes, name, category
  276. FROM fee
  277. WHERE loan_id = ?`
  278. rows, err := db.Query(query, loan)
  279. if err != nil {
  280. return nil, fmt.Errorf("Fee query error %v", err)
  281. }
  282. defer rows.Close()
  283. for rows.Next() {
  284. var fee Fee
  285. if err := rows.Scan(
  286. &fee.Id,
  287. &fee.LoanId,
  288. &fee.Amount,
  289. &fee.Perc,
  290. &fee.Type,
  291. &fee.Notes,
  292. &fee.Name,
  293. &fee.Category,
  294. )
  295. err != nil {
  296. return nil, fmt.Errorf("Fees scanning error: %v", err)
  297. }
  298. fees = append(fees, fee)
  299. }
  300. return fees, nil
  301. }
  302. func fetchFeesTemp(db *sql.DB, user int, branch int) ([]FeeTemplate, error) {
  303. var fees []FeeTemplate
  304. query := `SELECT
  305. id, user_id, COALESCE(branch_id, 0), amount, perc, type, notes, name,
  306. category, auto
  307. FROM fee_template
  308. WHERE user_id = ? OR branch_id = ?
  309. `
  310. rows, err := db.Query(query, user, branch)
  311. if err != nil {
  312. return nil, fmt.Errorf("Fee template query error %v", err)
  313. }
  314. defer rows.Close()
  315. for rows.Next() {
  316. var fee FeeTemplate
  317. if err := rows.Scan(
  318. &fee.Id,
  319. &fee.User,
  320. &fee.Branch,
  321. &fee.Amount,
  322. &fee.Perc,
  323. &fee.Type,
  324. &fee.Notes,
  325. &fee.Name,
  326. &fee.Category,
  327. &fee.Auto)
  328. err != nil {
  329. return nil, fmt.Errorf("FeesTemplate scanning error: %v", err)
  330. }
  331. fees = append(fees, fee)
  332. }
  333. return fees, nil
  334. }
  335. // Fetch fees from the database
  336. func getFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  337. var fees []FeeTemplate
  338. claims, err := getClaims(r)
  339. if err != nil { w.WriteHeader(500); return }
  340. users, err := queryUsers(db, claims.Id)
  341. if err != nil { w.WriteHeader(422); return }
  342. fees, err = fetchFeesTemp(db, claims.Id, users[0].BranchId)
  343. json.NewEncoder(w).Encode(fees)
  344. }
  345. // Fetch fees from the database
  346. func createFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  347. var fee FeeTemplate
  348. var query string
  349. var row *sql.Row
  350. var err error
  351. claims, err := getClaims(r)
  352. // var id int // Inserted estimate's id
  353. err = json.NewDecoder(r.Body).Decode(&fee)
  354. if err != nil { w.WriteHeader(422); return }
  355. query = `INSERT INTO fee_template
  356. (
  357. user_id,
  358. branch_id,
  359. amount,
  360. perc,
  361. type,
  362. notes,
  363. name,
  364. auto
  365. )
  366. VALUES (?, NULL, ?, ?, ?, ?, ?, ?)
  367. RETURNING id
  368. `
  369. row = db.QueryRow(query,
  370. claims.Id,
  371. fee.Amount,
  372. fee.Perc,
  373. fee.Type,
  374. fee.Notes,
  375. fee.Name,
  376. fee.Auto,
  377. )
  378. err = row.Scan(&fee.Id)
  379. if err != nil { w.WriteHeader(500); return }
  380. json.NewEncoder(w).Encode(fee)
  381. }
  382. // Fetch fees from the database
  383. func deleteFeeTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  384. var fee FeeTemplate
  385. var query string
  386. var err error
  387. // claims, err := getClaims(r)
  388. // var id int // Inserted estimate's id
  389. err = json.NewDecoder(r.Body).Decode(&fee)
  390. if err != nil { w.WriteHeader(422); return }
  391. query = `DELETE FROM fee_template WHERE id = ?`
  392. _, err = db.Exec(query, fee.Id)
  393. if err != nil { w.WriteHeader(500); return }
  394. }
  395. func getMi(db *sql.DB, loan int) (MI, error) {
  396. var mi MI
  397. query := `SELECT
  398. type, label, lender, rate, premium, upfront, five_year_total,
  399. initial_premium, initial_rate, initial_amount
  400. FROM mi WHERE loan_id = ?`
  401. rows, err := db.Query(query, loan)
  402. if err != nil { return mi, err }
  403. defer rows.Close()
  404. if (!rows.Next()) { return mi, nil }
  405. if err := rows.Scan(
  406. &mi.Type,
  407. &mi.Label,
  408. &mi.Lender,
  409. &mi.Rate,
  410. &mi.Premium,
  411. &mi.Upfront,
  412. &mi.FiveYearTotal,
  413. &mi.InitialAllInPremium,
  414. &mi.InitialAllInRate,
  415. &mi.InitialAmount,
  416. )
  417. err != nil {
  418. return mi, err
  419. }
  420. return mi, nil
  421. }
  422. func getBorrower(db *sql.DB, id int) (Borrower, error) {
  423. var borrower Borrower
  424. row := db.QueryRow(
  425. "SELECT * FROM borrower " +
  426. "WHERE id = ? LIMIT 1",
  427. id)
  428. if err := row.Scan(
  429. &borrower.Id,
  430. &borrower.Credit,
  431. &borrower.Income,
  432. &borrower.Num,
  433. )
  434. err != nil {
  435. return borrower, fmt.Errorf("Borrower scanning error: %v", err)
  436. }
  437. return borrower, nil
  438. }
  439. // Query Lender APIs and parse responses into MI structs
  440. func fetchMi(db *sql.DB, estimate *Estimate, pos int) []MI {
  441. var err error
  442. var loan Loan = estimate.Loans[pos]
  443. var ltv = func(l float32) string {
  444. switch {
  445. case l > 95: return "LTV97"
  446. case l > 90: return "LTV95"
  447. case l > 85: return "LTV90"
  448. default: return "LTV85"
  449. }
  450. }
  451. var term = func(t int) string {
  452. switch {
  453. case t <= 10: return "A10"
  454. case t <= 15: return "A15"
  455. case t <= 20: return "A20"
  456. case t <= 25: return "A25"
  457. case t <= 30: return "A30"
  458. default: return "A40"
  459. }
  460. }
  461. var propertyCodes = map[string]string {
  462. "Single Attached": "SFO",
  463. "Single Detached": "SFO",
  464. "Condo Lo-rise": "CON",
  465. "Condo Hi-rise": "CON",
  466. }
  467. var purposeCodes = map[string]string {
  468. "Purchase": "PUR",
  469. "Refinance": "RRT",
  470. }
  471. body, err := json.Marshal(map[string]any{
  472. "zipCode": estimate.Zip,
  473. "stateCode": "CA",
  474. "address": "",
  475. "propertyTypeCode": propertyCodes[estimate.Property],
  476. "occupancyTypeCode": "PRS",
  477. "loanPurposeCode": purposeCodes[estimate.Transaction],
  478. "loanAmount": loan.Amount,
  479. "loanToValue": ltv(loan.Ltv),
  480. "amortizationTerm": term(loan.Term),
  481. "loanTypeCode": "FXD",
  482. "duLpDecisionCode": "DAE",
  483. "loanProgramCodes": []any{},
  484. "debtToIncome": loan.Dti,
  485. "wholesaleLoan": 0,
  486. "coveragePercentageCode": "L30",
  487. "productCode": "BPM",
  488. "renewalTypeCode": "CON",
  489. "numberOfBorrowers": 1,
  490. "coBorrowerCreditScores": []any{},
  491. "borrowerCreditScore": strconv.Itoa(estimate.Borrower.Credit),
  492. "masterPolicy": nil,
  493. "selfEmployedIndicator": false,
  494. "armType": "",
  495. "userId": 44504,
  496. })
  497. if err != nil {
  498. log.Printf("Could not marshal NationalMI body: \n%v\n%v\n",
  499. bytes.NewBuffer(body), err)
  500. }
  501. req, err := http.NewRequest("POST",
  502. "https://rate-gps.nationalmi.com/rates/productRateQuote",
  503. bytes.NewBuffer(body))
  504. req.Header.Add("Content-Type", "application/json")
  505. req.AddCookie(&http.Cookie{
  506. Name: "nmirategps_email",
  507. Value: config["NationalMIEmail"]})
  508. resp, err := http.DefaultClient.Do(req)
  509. var res map[string]interface{}
  510. var result []MI
  511. if resp.StatusCode != 200 {
  512. log.Printf("the status: %v\nthe resp: %v\n the req: %v\n the body: %v\n",
  513. resp.Status, resp, req.Body, bytes.NewBuffer(body))
  514. } else {
  515. json.NewDecoder(resp.Body).Decode(&res)
  516. // estimate.Loans[pos].Mi = res
  517. // Parse res into result here
  518. }
  519. return result
  520. }
  521. // Make comparison PDF
  522. func generatePDF(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  523. }
  524. func login(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  525. var id int
  526. var role string
  527. var err error
  528. var user User
  529. json.NewDecoder(r.Body).Decode(&user)
  530. row := db.QueryRow(
  531. `SELECT id, role FROM user WHERE email = ? AND password = sha2(?, 256)`,
  532. user.Email, user.Password,
  533. )
  534. err = row.Scan(&id, &role)
  535. if err != nil {
  536. http.Error(w, "Invalid Credentials.", http.StatusUnauthorized)
  537. return
  538. }
  539. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  540. UserClaims{ Id: id, Role: role,
  541. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  542. tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
  543. if err != nil {
  544. log.Println("Token could not be signed: ", err, tokenStr)
  545. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  546. return
  547. }
  548. cookie := http.Cookie{Name: "skouter",
  549. Value: tokenStr,
  550. Path: "/",
  551. Expires: time.Now().Add(time.Hour * 24)}
  552. http.SetCookie(w, &cookie)
  553. _, err = w.Write([]byte(tokenStr))
  554. if err != nil {
  555. http.Error(w,
  556. "Could not complete token write.",
  557. http.StatusInternalServerError)}
  558. }
  559. func getToken(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  560. claims, err := getClaims(r)
  561. // Will verify existing signature and expiry time
  562. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  563. UserClaims{ Id: claims.Id, Role: claims.Role,
  564. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  565. tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
  566. if err != nil {
  567. log.Println("Token could not be signed: ", err, tokenStr)
  568. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  569. return
  570. }
  571. cookie := http.Cookie{Name: "skouter",
  572. Value: tokenStr,
  573. Path: "/",
  574. Expires: time.Now().Add(time.Hour * 24)}
  575. http.SetCookie(w, &cookie)
  576. _, err = w.Write([]byte(tokenStr))
  577. if err != nil {
  578. http.Error(w,
  579. "Could not complete token write.",
  580. http.StatusInternalServerError)}
  581. }
  582. func getClaims(r *http.Request) (UserClaims, error) {
  583. claims := new(UserClaims)
  584. _, tokenStr, found := strings.Cut(r.Header.Get("Authorization"), " ")
  585. if !found {
  586. return *claims, errors.New("Token not found")
  587. }
  588. // Pull token payload into UserClaims
  589. _, err := jwt.ParseWithClaims(tokenStr, claims,
  590. func(token *jwt.Token) (any, error) {
  591. return []byte(config["JWT_SECRET"]), nil
  592. })
  593. if err != nil {
  594. return *claims, err
  595. }
  596. if err = claims.Valid(); err != nil {
  597. return *claims, err
  598. }
  599. return *claims, nil
  600. }
  601. func guard(r *http.Request, required int) bool {
  602. claims, err := getClaims(r)
  603. if err != nil { return false }
  604. if roles[claims.Role] < required { return false }
  605. return true
  606. }
  607. func queryUsers(db *sql.DB, id int) ( []User, error ) {
  608. var users []User
  609. var query string
  610. var rows *sql.Rows
  611. var err error
  612. query = `SELECT
  613. u.id,
  614. u.email,
  615. u.first_name,
  616. u.last_name,
  617. u.branch_id,
  618. u.country,
  619. u.title,
  620. u.status,
  621. u.verified,
  622. u.role
  623. FROM user u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
  624. `
  625. rows, err = db.Query(query, id)
  626. if err != nil {
  627. return users, err
  628. }
  629. defer rows.Close()
  630. for rows.Next() {
  631. var user User
  632. if err := rows.Scan(
  633. &user.Id,
  634. &user.Email,
  635. &user.FirstName,
  636. &user.LastName,
  637. &user.BranchId,
  638. &user.Country,
  639. &user.Title,
  640. &user.Status,
  641. &user.Verified,
  642. &user.Role,
  643. )
  644. err != nil {
  645. return users, err
  646. }
  647. users = append(users, user)
  648. }
  649. // Prevents runtime panics
  650. if len(users) == 0 { return users, errors.New("User not found.") }
  651. return users, nil
  652. }
  653. func insertResults(db *sql.DB, results []Result) (error){
  654. var query string
  655. var row *sql.Row
  656. var err error
  657. query = `INSERT INTO estimate_result
  658. (
  659. loan_id,
  660. loan_payment,
  661. total_monthly,
  662. total_fees,
  663. total_credits,
  664. cash_to_close
  665. )
  666. VALUES (?, ?, ?, ?, ?, ?, ?)
  667. RETURNING id
  668. `
  669. for i := range results {
  670. db.QueryRow(query,
  671. results[i].LoanId,
  672. results[i].LoanPayment,
  673. results[i].TotalMonthly,
  674. results[i].TotalFees,
  675. results[i].TotalCredits,
  676. results[i].CashToClose,
  677. )
  678. err = row.Scan(&results[i].Id)
  679. if err != nil { return err }
  680. }
  681. return nil
  682. }
  683. func insertUser(db *sql.DB, user User) (User, error){
  684. var query string
  685. var row *sql.Row
  686. var err error
  687. var id int // Inserted user's id
  688. query = `INSERT INTO user
  689. (
  690. email,
  691. first_name,
  692. last_name,
  693. password,
  694. created,
  695. role,
  696. verified,
  697. last_login
  698. )
  699. VALUES (?, ?, ?, sha2(?, 256), NOW(), ?, ?, NOW())
  700. RETURNING id
  701. `
  702. row = db.QueryRow(query,
  703. user.Email,
  704. user.FirstName,
  705. user.LastName,
  706. user.Password,
  707. user.Role,
  708. user.Verified,
  709. )
  710. err = row.Scan(&id)
  711. if err != nil { return User{}, err }
  712. users, err := queryUsers(db, id)
  713. if err != nil { return User{}, err }
  714. return users[0], nil
  715. }
  716. func updateUser(user User, db *sql.DB) error {
  717. query := `
  718. UPDATE user
  719. SET
  720. email = CASE @e := ? WHEN '' THEN email ELSE @e END,
  721. first_name = CASE @fn := ? WHEN '' THEN first_name ELSE @fn END,
  722. last_name = CASE @ln := ? WHEN '' THEN last_name ELSE @ln END,
  723. role = CASE @r := ? WHEN '' THEN role ELSE @r END,
  724. password = CASE @p := ? WHEN '' THEN password ELSE sha2(@p, 256) END
  725. WHERE id = ?
  726. `
  727. _, err := db.Exec(query,
  728. user.Email,
  729. user.FirstName,
  730. user.LastName,
  731. user.Role,
  732. user.Password,
  733. user.Id,
  734. )
  735. return err
  736. }
  737. func getUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  738. claims, err := getClaims(r)
  739. if err != nil { w.WriteHeader(500); return }
  740. users, err := queryUsers(db, claims.Id)
  741. if err != nil { w.WriteHeader(422); log.Println(err); return }
  742. json.NewEncoder(w).Encode(users)
  743. }
  744. func getUsers(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  745. users, err := queryUsers(db, 0)
  746. if err != nil {
  747. w.WriteHeader(http.StatusInternalServerError)
  748. return
  749. }
  750. json.NewEncoder(w).Encode(users)
  751. }
  752. // Updates a user using only specified values in the JSON body
  753. func patchUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  754. var user User
  755. err := json.NewDecoder(r.Body).Decode(&user)
  756. _, err = mail.ParseAddress(user.Email)
  757. if err != nil { http.Error(w, "Invalid email.", 422); return }
  758. if roles[user.Role] == 0 {
  759. http.Error(w, "Invalid role.", 422)
  760. return
  761. }
  762. err = updateUser(user, db)
  763. if err != nil { http.Error(w, "Bad form values.", 422); return }
  764. users, err := queryUsers(db, user.Id)
  765. if err != nil { http.Error(w, "Bad form values.", 422); return }
  766. json.NewEncoder(w).Encode(users[0])
  767. }
  768. // Update specified fields of the user specified in the claim
  769. func patchSelf(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  770. claim, err := getClaims(r)
  771. var user User
  772. json.NewDecoder(r.Body).Decode(&user)
  773. // First check that the target user to be updated is the same as the claim id, and
  774. // their role is unchanged.
  775. if err != nil || claim.Id != user.Id {
  776. http.Error(w, "Target user's id does not match claim.", 401)
  777. return
  778. }
  779. if claim.Role != user.Role && user.Role != "" {
  780. http.Error(w, "Administrator required to escalate role.", 401)
  781. return
  782. }
  783. patchUser(w, db, r)
  784. }
  785. func deleteUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  786. var user User
  787. err := json.NewDecoder(r.Body).Decode(&user)
  788. if err != nil {
  789. http.Error(w, "Invalid fields.", 422)
  790. return
  791. }
  792. query := `DELETE FROM user WHERE id = ?`
  793. _, err = db.Exec(query, user.Id)
  794. if err != nil {
  795. http.Error(w, "Could not delete.", 500)
  796. }
  797. }
  798. func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  799. var user User
  800. err := json.NewDecoder(r.Body).Decode(&user)
  801. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  802. _, err = mail.ParseAddress(user.Email)
  803. if err != nil { http.Error(w, "Invalid email.", 422); return }
  804. if roles[user.Role] == 0 {
  805. http.Error(w, "Invalid role.", 422)
  806. }
  807. user, err = insertUser(db, user)
  808. if err != nil { http.Error(w, "Error creating user.", 422); return }
  809. json.NewEncoder(w).Encode(user)
  810. }
  811. func fetchAvatar(db *sql.DB, user int) ( []byte, error ) {
  812. var img []byte
  813. var query string
  814. var err error
  815. query = `SELECT
  816. avatar
  817. FROM user WHERE user.id = ?
  818. `
  819. row := db.QueryRow(query, user)
  820. err = row.Scan(&img)
  821. if err != nil {
  822. return img, err
  823. }
  824. return img, nil
  825. }
  826. func insertAvatar(db *sql.DB, user int, img []byte) error {
  827. query := `UPDATE user
  828. SET avatar = ?
  829. WHERE id = ?
  830. `
  831. _, err := db.Exec(query, img, user)
  832. if err != nil {
  833. return err
  834. }
  835. return nil
  836. }
  837. func fetchLetterhead(db *sql.DB, user int) ( []byte, error ) {
  838. var img []byte
  839. var query string
  840. var err error
  841. query = `SELECT
  842. letterhead
  843. FROM user WHERE user.id = ?
  844. `
  845. row := db.QueryRow(query, user)
  846. err = row.Scan(&img)
  847. if err != nil {
  848. return img, err
  849. }
  850. return img, nil
  851. }
  852. func insertLetterhead(db *sql.DB, user int, img []byte) error {
  853. query := `UPDATE user
  854. SET letterhead = ?
  855. WHERE id = ?
  856. `
  857. _, err := db.Exec(query, img, user)
  858. if err != nil {
  859. return err
  860. }
  861. return nil
  862. }
  863. func setAvatar(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  864. var validTypes []string = []string{"image/png", "image/jpeg"}
  865. var isValidType bool
  866. claims, err := getClaims(r)
  867. if err != nil { http.Error(w, "Invalid token.", 422); return }
  868. img, err := io.ReadAll(r.Body)
  869. if err != nil { http.Error(w, "Invalid file.", 422); return }
  870. for _, v := range validTypes {
  871. if v == http.DetectContentType(img) { isValidType = true }
  872. }
  873. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  874. err = insertAvatar(db, claims.Id, img)
  875. if err != nil { http.Error(w, "Could not insert.", 500); return }
  876. }
  877. func getAvatar(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  878. claims, err := getClaims(r)
  879. if err != nil { http.Error(w, "Invalid token.", 422); return }
  880. img, err := fetchAvatar(db, claims.Id)
  881. if err != nil { http.Error(w, "Could not retrieve.", 500); return }
  882. w.Header().Set("Content-Type", http.DetectContentType(img))
  883. w.Write(img)
  884. }
  885. func setLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  886. var validTypes []string = []string{"image/png", "image/jpeg"}
  887. var isValidType bool
  888. claims, err := getClaims(r)
  889. if err != nil { http.Error(w, "Invalid token.", 422); return }
  890. img, err := io.ReadAll(r.Body)
  891. if err != nil { http.Error(w, "Invalid file.", 422); return }
  892. for _, v := range validTypes {
  893. if v == http.DetectContentType(img) { isValidType = true }
  894. }
  895. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  896. err = insertLetterhead(db, claims.Id, img)
  897. if err != nil { http.Error(w, "Could not insert.", 500); return }
  898. }
  899. func getLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  900. claims, err := getClaims(r)
  901. if err != nil { http.Error(w, "Invalid token.", 422); return }
  902. img, err := fetchLetterhead(db, claims.Id)
  903. if err != nil { http.Error(w, "Could not retrieve.", 500); return }
  904. w.Header().Set("Content-Type", http.DetectContentType(img))
  905. w.Write(img)
  906. }
  907. func queryBorrower(db *sql.DB, id int) ( Borrower, error ) {
  908. var borrower Borrower
  909. var query string
  910. var err error
  911. query = `SELECT
  912. l.id,
  913. l.credit_score,
  914. l.num,
  915. l.monthly_income
  916. FROM borrower l WHERE l.id = ?
  917. `
  918. row := db.QueryRow(query, id)
  919. err = row.Scan(
  920. borrower.Id,
  921. borrower.Credit,
  922. borrower.Num,
  923. borrower.Income,
  924. )
  925. if err != nil {
  926. return borrower, err
  927. }
  928. return borrower, nil
  929. }
  930. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  931. func getResults(db *sql.DB, e int, id int) ( []Result, error ) {
  932. var results []Result
  933. var query string
  934. var rows *sql.Rows
  935. var err error
  936. query = `SELECT
  937. r.id,
  938. loan_id,
  939. loan_payment,
  940. total_monthly,
  941. total_fees,
  942. total_credits,
  943. cash_to_close
  944. FROM estimate_result r
  945. INNER JOIN loan
  946. ON r.loan_id = loan.id
  947. WHERE r.id = CASE @e := ? WHEN 0 THEN r.id ELSE @e END
  948. AND loan.estimate_id = ?
  949. `
  950. rows, err = db.Query(query, id, e)
  951. if err != nil {
  952. return results, err
  953. }
  954. defer rows.Close()
  955. for rows.Next() {
  956. var result Result
  957. if err := rows.Scan(
  958. &result.Id,
  959. &result.LoanId,
  960. &result.LoanPayment,
  961. &result.TotalMonthly,
  962. &result.TotalFees,
  963. &result.TotalCredits,
  964. &result.CashToClose,
  965. )
  966. err != nil {
  967. return results, err
  968. }
  969. results = append(results, result)
  970. }
  971. // Prevents runtime panics
  972. // if len(results) == 0 { return results, errors.New("Result not found.") }
  973. return results, nil
  974. }
  975. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  976. func getLoans(db *sql.DB, e int, id int) ( []Loan, error ) {
  977. var loans []Loan
  978. var query string
  979. var rows *sql.Rows
  980. var err error
  981. query = `SELECT
  982. l.id,
  983. l.type_id,
  984. l.estimate_id,
  985. l.amount,
  986. l.term,
  987. l.interest,
  988. l.ltv,
  989. l.dti,
  990. l.hoi,
  991. l.tax,
  992. l.name
  993. FROM loan l WHERE l.id = CASE @e := ? WHEN 0 THEN l.id ELSE @e END AND
  994. l.estimate_id = ?
  995. `
  996. rows, err = db.Query(query, id, e)
  997. if err != nil {
  998. return loans, err
  999. }
  1000. defer rows.Close()
  1001. for rows.Next() {
  1002. var loan Loan
  1003. if err := rows.Scan(
  1004. &loan.Id,
  1005. &loan.Type.Id,
  1006. &loan.EstimateId,
  1007. &loan.Amount,
  1008. &loan.Term,
  1009. &loan.Interest,
  1010. &loan.Ltv,
  1011. &loan.Dti,
  1012. &loan.Hoi,
  1013. &loan.Tax,
  1014. &loan.Name,
  1015. )
  1016. err != nil {
  1017. return loans, err
  1018. }
  1019. mi, err := getMi(db, loan.Id)
  1020. if err != nil {
  1021. return loans, err
  1022. }
  1023. loan.Mi = mi
  1024. fees, err := getFees(db, loan.Id)
  1025. if err != nil {
  1026. return loans, err
  1027. }
  1028. loan.Fees = fees
  1029. loan.Type, err = getLoanType(db, loan.Type.Id)
  1030. if err != nil {
  1031. return loans, err
  1032. }
  1033. loans = append(loans, loan)
  1034. }
  1035. // Prevents runtime panics
  1036. if len(loans) == 0 { return loans, errors.New("Loan not found.") }
  1037. return loans, nil
  1038. }
  1039. func getEstimates(db *sql.DB, id int, user int) ( []Estimate, error ) {
  1040. var estimates []Estimate
  1041. var query string
  1042. var rows *sql.Rows
  1043. var err error
  1044. query = `SELECT
  1045. id,
  1046. user_id,
  1047. borrower_id,
  1048. transaction,
  1049. price,
  1050. property,
  1051. occupancy,
  1052. zip,
  1053. pud
  1054. FROM estimate WHERE id = CASE @e := ? WHEN 0 THEN id ELSE @e END AND
  1055. user_id = CASE @e := ? WHEN 0 THEN user_id ELSE @e END
  1056. `
  1057. rows, err = db.Query(query, id, user)
  1058. if err != nil {
  1059. return estimates, err
  1060. }
  1061. defer rows.Close()
  1062. for rows.Next() {
  1063. var estimate Estimate
  1064. if err := rows.Scan(
  1065. &estimate.Id,
  1066. &estimate.User,
  1067. &estimate.Borrower.Id,
  1068. &estimate.Transaction,
  1069. &estimate.Price,
  1070. &estimate.Property,
  1071. &estimate.Occupancy,
  1072. &estimate.Zip,
  1073. &estimate.Pud,
  1074. )
  1075. err != nil {
  1076. return estimates, err
  1077. }
  1078. borrower, err := getBorrower(db, estimate.Borrower.Id)
  1079. if err != nil {
  1080. return estimates, err
  1081. }
  1082. estimate.Borrower = borrower
  1083. estimate.Results, err = getResults(db, estimate.Id, 0)
  1084. if err != nil {
  1085. return estimates, err
  1086. }
  1087. estimates = append(estimates, estimate)
  1088. }
  1089. // Prevents runtime panics
  1090. if len(estimates) == 0 { return estimates, errors.New("Estimate not found.") }
  1091. for i := range estimates {
  1092. estimates[i].Loans, err = getLoans(db, estimates[i].Id, 0)
  1093. if err != nil { return estimates, err }
  1094. }
  1095. return estimates, nil
  1096. }
  1097. // Accepts a borrower struct and returns the id of the inserted borrower and
  1098. // any related error.
  1099. func insertBorrower(db *sql.DB, borrower Borrower) (int, error) {
  1100. var query string
  1101. var row *sql.Row
  1102. var err error
  1103. var id int // Inserted loan's id
  1104. query = `INSERT INTO borrower
  1105. (
  1106. credit_score,
  1107. monthly_income,
  1108. num
  1109. )
  1110. VALUES (?, ?, ?)
  1111. RETURNING id
  1112. `
  1113. row = db.QueryRow(query,
  1114. borrower.Credit,
  1115. borrower.Income,
  1116. borrower.Num,
  1117. )
  1118. err = row.Scan(&id)
  1119. if err != nil { return 0, err }
  1120. return id, nil
  1121. }
  1122. func insertMi(db *sql.DB, mi MI) (int, error) {
  1123. var id int
  1124. query := `INSERT INTO mi
  1125. (
  1126. type,
  1127. label,
  1128. lender,
  1129. rate,
  1130. premium,
  1131. upfront,
  1132. five_year_total,
  1133. initial_premium,
  1134. initial_rate,
  1135. initial_amount
  1136. )
  1137. VALUES (?, ?, ?, ?, ?, ?, ?)
  1138. RETURNING id`
  1139. row := db.QueryRow(query,
  1140. &mi.Type,
  1141. &mi.Label,
  1142. &mi.Lender,
  1143. &mi.Rate,
  1144. &mi.Premium,
  1145. &mi.Upfront,
  1146. &mi.FiveYearTotal,
  1147. &mi.InitialAllInPremium,
  1148. &mi.InitialAllInRate,
  1149. &mi.InitialAmount,
  1150. )
  1151. err := row.Scan(&id)
  1152. if err != nil { return 0, err }
  1153. return id, nil
  1154. }
  1155. func insertFee(db *sql.DB, fee Fee) (int, error) {
  1156. var id int
  1157. query := `INSERT INTO fee
  1158. (loan_id, amount, perc, type, notes, name, category)
  1159. VALUES (?, ?, ?, ?, ?, ?, ?)
  1160. RETURNING id`
  1161. row := db.QueryRow(query,
  1162. fee.LoanId,
  1163. fee.Amount,
  1164. fee.Perc,
  1165. fee.Type,
  1166. fee.Notes,
  1167. fee.Name,
  1168. fee.Category,
  1169. )
  1170. err := row.Scan(&id)
  1171. if err != nil { return 0, err }
  1172. return id, nil
  1173. }
  1174. func insertLoan(db *sql.DB, loan Loan) (Loan, error){
  1175. var query string
  1176. var row *sql.Row
  1177. var err error
  1178. var id int // Inserted loan's id
  1179. query = `INSERT INTO loan
  1180. (
  1181. estimate_id,
  1182. type_id,
  1183. amount,
  1184. term,
  1185. interest,
  1186. ltv,
  1187. dti,
  1188. hoi,
  1189. tax,
  1190. name
  1191. )
  1192. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1193. RETURNING id
  1194. `
  1195. row = db.QueryRow(query,
  1196. loan.EstimateId,
  1197. loan.Type.Id,
  1198. loan.Amount,
  1199. loan.Term,
  1200. loan.Interest,
  1201. loan.Ltv,
  1202. loan.Dti,
  1203. loan.Hoi,
  1204. loan.Tax,
  1205. loan.Name,
  1206. )
  1207. err = row.Scan(&id)
  1208. if err != nil { return loan, err }
  1209. _, err = insertMi(db, loan.Mi)
  1210. if err != nil { return loan, err }
  1211. for i := range loan.Fees {
  1212. _, err := insertFee(db, loan.Fees[i])
  1213. if err != nil { return loan, err }
  1214. }
  1215. loans, err := getLoans(db, id, 0)
  1216. if err != nil { return Loan{}, err }
  1217. return loans[0], nil
  1218. }
  1219. func insertEstimate(db *sql.DB, estimate Estimate) (Estimate, error){
  1220. var query string
  1221. var row *sql.Row
  1222. var err error
  1223. // var id int // Inserted estimate's id
  1224. estimate.Borrower.Id, err = insertBorrower(db, estimate.Borrower)
  1225. if err != nil { return Estimate{}, err }
  1226. query = `INSERT INTO estimate
  1227. (
  1228. user_id,
  1229. borrower_id,
  1230. transaction,
  1231. price,
  1232. property,
  1233. occupancy,
  1234. zip,
  1235. pud
  1236. )
  1237. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  1238. RETURNING id
  1239. `
  1240. row = db.QueryRow(query,
  1241. estimate.User,
  1242. estimate.Borrower.Id,
  1243. estimate.Transaction,
  1244. estimate.Price,
  1245. estimate.Property,
  1246. estimate.Occupancy,
  1247. estimate.Zip,
  1248. estimate.Pud,
  1249. )
  1250. err = row.Scan(&estimate.Id)
  1251. if err != nil { return Estimate{}, err }
  1252. for _, l := range estimate.Loans {
  1253. l.EstimateId = estimate.Id
  1254. _, err = insertLoan(db, l)
  1255. if err != nil { return estimate, err }
  1256. }
  1257. estimates, err := getEstimates(db, estimate.Id, 0)
  1258. if err != nil { return Estimate{}, err }
  1259. return estimates[0], nil
  1260. }
  1261. func createEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1262. var estimate Estimate
  1263. err := json.NewDecoder(r.Body).Decode(&estimate)
  1264. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  1265. claims, err := getClaims(r)
  1266. estimate.User = claims.Id
  1267. estimate, err = insertEstimate(db, estimate)
  1268. if err != nil { http.Error(w, err.Error(), 422); return }
  1269. estimate.Results = makeResults(estimate)
  1270. err = insertResults(db, estimate.Results)
  1271. if err != nil { http.Error(w, err.Error(), 500); return }
  1272. e, err := getEstimates(db, estimate.Id, 0)
  1273. if err != nil { http.Error(w, err.Error(), 500); return }
  1274. json.NewEncoder(w).Encode(e[0])
  1275. }
  1276. // Query all estimates that belong to the current user
  1277. func fetchEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1278. var estimates []Estimate
  1279. claims, err := getClaims(r)
  1280. estimates, err = getEstimates(db, 0, claims.Id)
  1281. if err != nil { http.Error(w, err.Error(), 500); return }
  1282. json.NewEncoder(w).Encode(estimates)
  1283. }
  1284. func checkConventional(l Loan, b Borrower) error {
  1285. if b.Credit < 620 {
  1286. return errors.New("Credit score too low for conventional loan")
  1287. }
  1288. // Buyer needs to put down 20% to avoid mortgage insurance
  1289. if (l.Ltv > 80 && l.Mi.Rate == 0) {
  1290. return errors.New(fmt.Sprintln(
  1291. l.Name,
  1292. "down payment must be 20% to avoid insurance",
  1293. ))
  1294. }
  1295. return nil
  1296. }
  1297. func checkFHA(l Loan, b Borrower) error {
  1298. if b.Credit < 500 {
  1299. return errors.New("Credit score too low for FHA loan")
  1300. }
  1301. if (l.Ltv > 96.5) {
  1302. return errors.New("FHA down payment must be at least 3.5%")
  1303. }
  1304. if (b.Credit < 580 && l.Ltv > 90) {
  1305. return errors.New("FHA down payment must be at least 10%")
  1306. }
  1307. // Debt-to-income must be below 45% if credit score is below 580.
  1308. if (b.Credit < 580 && l.Dti > 45) {
  1309. return errors.New(fmt.Sprintln(
  1310. l.Name, "debt to income is too high for credit score.",
  1311. ))
  1312. }
  1313. return nil
  1314. }
  1315. // Loan option for veterans with no set rules
  1316. func checkVA(l Loan, b Borrower) error {
  1317. return nil
  1318. }
  1319. // Loan option for residents of rural areas with no set rules
  1320. func checkUSDA(l Loan, b Borrower) error {
  1321. return nil
  1322. }
  1323. // Should also check loan amount limit maybe with an API.
  1324. func checkEstimate(e Estimate) error {
  1325. if e.Property == "" { return errors.New("Empty property type") }
  1326. if e.Price == 0 { return errors.New("Empty property price") }
  1327. if e.Borrower.Num == 0 {
  1328. return errors.New("Missing number of borrowers")
  1329. }
  1330. if e.Borrower.Credit == 0 {
  1331. return errors.New("Missing borrower credit score")
  1332. }
  1333. if e.Borrower.Income == 0 {
  1334. return errors.New("Missing borrower credit income")
  1335. }
  1336. for _, l := range e.Loans {
  1337. if l.Amount == 0 {
  1338. return errors.New(fmt.Sprintln(l.Name, "loan amount cannot be zero"))
  1339. }
  1340. if l.Term == 0 {
  1341. return errors.New(fmt.Sprintln(l.Name, "loan term cannot be zero"))
  1342. }
  1343. if l.Interest == 0 {
  1344. return errors.New(fmt.Sprintln(l.Name, "loan interest cannot be zero"))
  1345. }
  1346. // Can be used to check rules for specific loan types
  1347. var err error
  1348. switch l.Type.Id {
  1349. case 1:
  1350. err = checkConventional(l, e.Borrower)
  1351. case 2:
  1352. err = checkFHA(l, e.Borrower)
  1353. case 3:
  1354. err = checkVA(l, e.Borrower)
  1355. case 4:
  1356. err = checkUSDA(l, e.Borrower)
  1357. default:
  1358. err = errors.New("Invalid loan type")
  1359. }
  1360. if err != nil { return err }
  1361. }
  1362. return nil
  1363. }
  1364. func validateEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1365. var estimate Estimate
  1366. err := json.NewDecoder(r.Body).Decode(&estimate)
  1367. if err != nil { http.Error(w, err.Error(), 422); return }
  1368. err = checkEstimate(estimate)
  1369. if err != nil { http.Error(w, err.Error(), 406); return }
  1370. }
  1371. func showPDF(w http.ResponseWriter, r *http.Request) {
  1372. // var args []string
  1373. // p := r.URL.Path
  1374. db, err := sql.Open("mysql",
  1375. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
  1376. config["DBUser"],
  1377. config["DBPass"]))
  1378. // w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  1379. err = db.Ping()
  1380. if err != nil {
  1381. fmt.Println("Bad database configuration: %v\n", err)
  1382. panic(err)
  1383. // maybe os.Exit(1) instead
  1384. }
  1385. var pa = template.Must(template.ParseFiles("views/master.tpl",
  1386. "views/test.tpl"))
  1387. // claims, err := getClaims(r)
  1388. if err != nil { w.WriteHeader(500); return }
  1389. users, err := queryUsers(db, 1)
  1390. info := struct {
  1391. Title string
  1392. Name string
  1393. User User
  1394. }{
  1395. Title: "test PDF",
  1396. Name: "idk-random-name",
  1397. User: users[0],
  1398. }
  1399. // fmt.Println(info)
  1400. err = pa.Execute(w, info)
  1401. if err != nil {fmt.Println(err)}
  1402. }
  1403. func api(w http.ResponseWriter, r *http.Request) {
  1404. var args []string
  1405. p := r.URL.Path
  1406. db, err := sql.Open("mysql",
  1407. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
  1408. config["DBUser"],
  1409. config["DBPass"]))
  1410. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  1411. err = db.Ping()
  1412. if err != nil {
  1413. fmt.Println("Bad database configuration: %v\n", err)
  1414. panic(err)
  1415. // maybe os.Exit(1) instead
  1416. }
  1417. switch {
  1418. case match(p, "/api/login", &args) &&
  1419. r.Method == http.MethodPost:
  1420. login(w, db, r)
  1421. case match(p, "/api/token", &args) &&
  1422. r.Method == http.MethodGet && guard(r, 1):
  1423. getToken(w, db, r)
  1424. case match(p, "/api/users", &args) && // Array of all users
  1425. r.Method == http.MethodGet && guard(r, 3):
  1426. getUsers(w, db, r)
  1427. case match(p, "/api/user", &args) &&
  1428. r.Method == http.MethodGet && guard(r, 1):
  1429. getUser(w, db, r)
  1430. case match(p, "/api/user", &args) &&
  1431. r.Method == http.MethodPost &&
  1432. guard(r, 3):
  1433. createUser(w, db, r)
  1434. case match(p, "/api/user", &args) &&
  1435. r.Method == http.MethodPatch &&
  1436. guard(r, 3): // For admin to modify any user
  1437. patchUser(w, db, r)
  1438. case match(p, "/api/user", &args) &&
  1439. r.Method == http.MethodPatch &&
  1440. guard(r, 2): // For employees to modify own accounts
  1441. patchSelf(w, db, r)
  1442. case match(p, "/api/user", &args) &&
  1443. r.Method == http.MethodDelete &&
  1444. guard(r, 3):
  1445. deleteUser(w, db, r)
  1446. case match(p, "/api/user/avatar", &args) &&
  1447. r.Method == http.MethodGet &&
  1448. guard(r, 1):
  1449. getAvatar(w, db, r)
  1450. case match(p, "/api/user/avatar", &args) &&
  1451. r.Method == http.MethodPost &&
  1452. guard(r, 1):
  1453. setAvatar(w, db, r)
  1454. case match(p, "/api/user/letterhead", &args) &&
  1455. r.Method == http.MethodGet &&
  1456. guard(r, 1):
  1457. getLetterhead(w, db, r)
  1458. case match(p, "/api/user/letterhead", &args) &&
  1459. r.Method == http.MethodPost &&
  1460. guard(r, 1):
  1461. setLetterhead(w, db, r)
  1462. case match(p, "/api/fees", &args) &&
  1463. r.Method == http.MethodGet &&
  1464. guard(r, 1):
  1465. getFeesTemp(w, db, r)
  1466. case match(p, "/api/fee", &args) &&
  1467. r.Method == http.MethodPost &&
  1468. guard(r, 1):
  1469. createFeesTemp(w, db, r)
  1470. case match(p, "/api/fee", &args) &&
  1471. r.Method == http.MethodDelete &&
  1472. guard(r, 1):
  1473. deleteFeeTemp(w, db, r)
  1474. case match(p, "/api/estimates", &args) &&
  1475. r.Method == http.MethodGet &&
  1476. guard(r, 1):
  1477. fetchEstimate(w, db, r)
  1478. case match(p, "/api/estimate", &args) &&
  1479. r.Method == http.MethodPost &&
  1480. guard(r, 1):
  1481. createEstimate(w, db, r)
  1482. case match(p, "/api/estimate/validate", &args) &&
  1483. r.Method == http.MethodPost &&
  1484. guard(r, 1):
  1485. validateEstimate(w, db, r)
  1486. case match(p, "/api/estimate/summarize", &args) &&
  1487. r.Method == http.MethodPost &&
  1488. guard(r, 1):
  1489. summarize(w, db, r)
  1490. default:
  1491. http.Error(w, "Invalid route or token", 404)
  1492. }
  1493. db.Close()
  1494. }
  1495. func route(w http.ResponseWriter, r *http.Request) {
  1496. var page Page
  1497. var args []string
  1498. p := r.URL.Path
  1499. switch {
  1500. case r.Method == "GET" && match(p, "/", &args):
  1501. page = pages[ "home" ]
  1502. case match(p, "/terms", &args):
  1503. page = pages[ "terms" ]
  1504. case match(p, "/app", &args):
  1505. page = pages[ "app" ]
  1506. case match(p, "/test", &args):
  1507. showPDF(w, r)
  1508. return
  1509. default:
  1510. http.NotFound(w, r)
  1511. return
  1512. }
  1513. page.Render(w)
  1514. }
  1515. func main() {
  1516. files := http.FileServer(http.Dir(""))
  1517. http.Handle("/assets/", files)
  1518. http.HandleFunc("/api/", api)
  1519. http.HandleFunc("/", route)
  1520. log.Fatal(http.ListenAndServe(address, nil))
  1521. }