Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

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