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.
 
 
 
 
 
 

852 lines
18 KiB

  1. package main
  2. import (
  3. "net/http"
  4. "log"
  5. "sync"
  6. "regexp"
  7. "html/template"
  8. "database/sql"
  9. _ "github.com/go-sql-driver/mysql"
  10. "fmt"
  11. "encoding/json"
  12. "strconv"
  13. "bytes"
  14. "time"
  15. "errors"
  16. "strings"
  17. pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
  18. "github.com/golang-jwt/jwt/v4"
  19. )
  20. type UserClaims struct {
  21. Id int `json:"id"`
  22. Role string `json:"role"`
  23. Exp string `json:"exp"`
  24. }
  25. type Page struct {
  26. tpl *template.Template
  27. Title string
  28. Name string
  29. }
  30. type Borrower struct {
  31. Id int `json:"id"`
  32. Credit int `json:"credit"`
  33. Income int `json:"income"`
  34. Num int `json:"num"`
  35. }
  36. type FeeTemplate struct {
  37. Id int `json:"id"`
  38. User int `json:"user"`
  39. Branch int `json:"branch"`
  40. Amount int `json:"amount"`
  41. Perc int `json:"perc"`
  42. Type string `json:"type"`
  43. Notes string `json:"notes"`
  44. Name string `json:"name"`
  45. Category string `json:"category"`
  46. Auto bool `json:"auto"`
  47. }
  48. type Fee struct {
  49. Id int `json:"id"`
  50. LoanId int `json:"loan_id"`
  51. Amount int `json:"amount"`
  52. Perc int `json:"perc"`
  53. Type string `json:"type"`
  54. Notes string `json:"notes"`
  55. Name string `json:"name"`
  56. Category string `json:"category"`
  57. }
  58. type LoanType struct {
  59. Id int `json:"id"`
  60. User int `json:"user"`
  61. Branch int `json:"branch"`
  62. Name string `json:"name"`
  63. }
  64. type Loan struct {
  65. Id int `json:id`
  66. EstimateId int `json:estimate_id`
  67. Type LoanType `json:"loanType"`
  68. Amount int `json:"loanAmount"`
  69. Term int `json:"term"`
  70. Ltv float32 `json:"ltv"`
  71. Dti float32 `json:"dti"`
  72. Hoi int `json:"hoi"`
  73. Interest int `json:"interest"`
  74. Mi MI `json:"mi"`
  75. Fees []Fee `json:"fees"`
  76. Name string `json:"name"`
  77. }
  78. type MI struct {
  79. Type string
  80. Label string
  81. Lender string
  82. Rate float32
  83. Premium float32
  84. Upfront float32
  85. FiveYearTotal float32
  86. InitialAllInPremium float32
  87. InitialAllInRate float32
  88. InitialAmount float32
  89. }
  90. type Estimate struct {
  91. Id int `json:"id"`
  92. User int `json:"user"`
  93. Borrower Borrower `json:"borrower"`
  94. Transaction string `json:"transaction"`
  95. Price int `json:"price"`
  96. Property string `json:"property"`
  97. Occupancy string `json:"occupancy"`
  98. Zip string `json:"zip"`
  99. Pud bool `json:"pud"`
  100. Loans []Loan `json:"loans"`
  101. }
  102. var (
  103. regexen = make(map[string]*regexp.Regexp)
  104. relock sync.Mutex
  105. address = "127.0.0.1:8001"
  106. )
  107. var paths = map[string]string {
  108. "home": "home.tpl",
  109. "terms": "terms.tpl",
  110. "app": "app.tpl",
  111. }
  112. var pages = map[string]Page {
  113. "home": cache("home", "Home"),
  114. "terms": cache("terms", "Terms and Conditions"),
  115. "app": cache("app", "App"),
  116. }
  117. var roles = map[string]int{
  118. "User": 1,
  119. "Manager": 2,
  120. "Admin": 3,
  121. }
  122. // Used to validate claim in JWT token body. Checks if user id is greater than
  123. // zero and time format is valid
  124. func (c UserClaims) Valid() error {
  125. if c.Id < 1 { return errors.New("Invalid id") }
  126. t, err := time.Parse(time.UnixDate, c.Exp)
  127. if err != nil { return err }
  128. if t.Before(time.Now()) { return errors.New("Token expired.") }
  129. return err
  130. }
  131. func cache(name string, title string) Page {
  132. var p = []string{"master.tpl", paths[name]}
  133. tpl := template.Must(template.ParseFiles(p...))
  134. return Page{tpl: tpl,
  135. Title: title,
  136. Name: name,
  137. }
  138. }
  139. func (page Page) Render(w http.ResponseWriter) {
  140. err := page.tpl.Execute(w, page)
  141. if err != nil {
  142. log.Print(err)
  143. }
  144. }
  145. func match(path, pattern string, args *[]string) bool {
  146. relock.Lock()
  147. defer relock.Unlock()
  148. regex := regexen[pattern]
  149. if regex == nil {
  150. regex = regexp.MustCompile("^" + pattern + "$")
  151. regexen[pattern] = regex
  152. }
  153. matches := regex.FindStringSubmatch(path)
  154. if len(matches) <= 0 {
  155. return false
  156. }
  157. *args = matches[1:]
  158. return true
  159. }
  160. func getLoanType(
  161. db *sql.DB,
  162. user int,
  163. branch int,
  164. isUser bool) ([]LoanType, error) {
  165. var loans []LoanType
  166. // Should be changed to specify user
  167. rows, err :=
  168. db.Query(`SELECT * FROM loan_type WHERE user_id = ? AND branch_id = ? ` +
  169. "OR (user_id = 0 AND branch_id = 0)", user, branch)
  170. if err != nil {
  171. return nil, fmt.Errorf("loan_type error: %v", err)
  172. }
  173. defer rows.Close()
  174. for rows.Next() {
  175. var loan LoanType
  176. if err := rows.Scan(
  177. &loan.Id,
  178. &loan.User,
  179. &loan.Branch,
  180. &loan.Name)
  181. err != nil {
  182. log.Printf("Error occured fetching loan: %v", err)
  183. return nil, fmt.Errorf("Error occured fetching loan: %v", err)
  184. }
  185. loans = append(loans, loan)
  186. }
  187. log.Printf("The loans: %v", loans)
  188. return loans, nil
  189. }
  190. func getEstimate(db *sql.DB, id int) (Estimate, error) {
  191. var estimate Estimate
  192. var err error
  193. query := `SELECT e.id, e.user_id, e.transaction,
  194. e.price, e.property, e.occupancy, e.zip, e.pud,
  195. b.id, b.credit_score, b.monthly_income, b.num
  196. FROM estimate e
  197. INNER JOIN borrower b ON e.borrower_id = b.id
  198. WHERE e.id = ?
  199. `
  200. // Inner join should always be valid because a borrower is a required
  201. // foreign key.
  202. row := db.QueryRow(query, id)
  203. if err = row.Scan(
  204. &estimate.Id,
  205. &estimate.User,
  206. &estimate.Transaction,
  207. &estimate.Price,
  208. &estimate.Property,
  209. &estimate.Occupancy,
  210. &estimate.Zip,
  211. &estimate.Pud,
  212. &estimate.Borrower.Id,
  213. &estimate.Borrower.Credit,
  214. &estimate.Borrower.Income,
  215. &estimate.Borrower.Num,
  216. )
  217. err != nil {
  218. return estimate, fmt.Errorf("Estimate scanning error: %v", err)
  219. }
  220. estimate.Loans, err = getLoans(db, estimate.Id)
  221. return estimate, err
  222. }
  223. func getFees(db *sql.DB, loan int) ([]Fee, error) {
  224. var fees []Fee
  225. rows, err := db.Query(
  226. "SELECT * FROM fees " +
  227. "WHERE loan_id = ?",
  228. loan)
  229. if err != nil {
  230. return nil, fmt.Errorf("Fee query error %v", err)
  231. }
  232. defer rows.Close()
  233. for rows.Next() {
  234. var fee Fee
  235. if err := rows.Scan(
  236. &fee.Id,
  237. &fee.LoanId,
  238. &fee.Amount,
  239. &fee.Perc,
  240. &fee.Type,
  241. &fee.Notes,
  242. &fee.Name,
  243. &fee.Category,
  244. )
  245. err != nil {
  246. return nil, fmt.Errorf("Fees scanning error: %v", err)
  247. }
  248. fees = append(fees, fee)
  249. }
  250. return fees, nil
  251. }
  252. // Fetch fees from the database
  253. func getFeesTemp(db *sql.DB, user int) ([]FeeTemplate, error) {
  254. var fees []FeeTemplate
  255. rows, err := db.Query(
  256. "SELECT * FROM fee_template " +
  257. "WHERE user_id = ? OR user_id = 0",
  258. user)
  259. if err != nil {
  260. return nil, fmt.Errorf("Fee template query error %v", err)
  261. }
  262. defer rows.Close()
  263. for rows.Next() {
  264. var fee FeeTemplate
  265. if err := rows.Scan(
  266. &fee.Id,
  267. &fee.User,
  268. &fee.Branch,
  269. &fee.Amount,
  270. &fee.Perc,
  271. &fee.Type,
  272. &fee.Notes,
  273. &fee.Name,
  274. &fee.Category,
  275. &fee.Auto)
  276. err != nil {
  277. return nil, fmt.Errorf("FeesTemplate scanning error: %v", err)
  278. }
  279. fees = append(fees, fee)
  280. }
  281. return fees, nil
  282. }
  283. func getMi(db *sql.DB, loan int) (MI, error) {
  284. var mi MI
  285. query := `SELECT
  286. type, label, lender, rate, premium, upfront, five_year_total,
  287. initial_premium, initial_rate, initial_amount
  288. FROM mi WHERE loan_id = ?`
  289. row := db.QueryRow(query, loan)
  290. if err := row.Scan(
  291. &mi.Type,
  292. &mi.Label,
  293. &mi.Lender,
  294. &mi.Rate,
  295. &mi.Premium,
  296. &mi.Upfront,
  297. &mi.FiveYearTotal,
  298. &mi.InitialAllInPremium,
  299. &mi.InitialAllInRate,
  300. &mi.InitialAmount,
  301. )
  302. err != nil {
  303. return mi, err
  304. }
  305. return mi, nil
  306. }
  307. func getLoans(db *sql.DB, estimate int) ([]Loan, error) {
  308. var loans []Loan
  309. query := `SELECT
  310. l.id, l.amount, l.term, l.interest, l.ltv, l.dti, l.hoi,
  311. lt.id, lt.user_id, lt.branch_id, lt.name
  312. FROM loan l INNER JOIN loan_type lt ON l.type_id = lt.id
  313. WHERE l.estimate_id = ?
  314. `
  315. rows, err := db.Query(query, estimate)
  316. if err != nil {
  317. return nil, fmt.Errorf("Loan query error %v", err)
  318. }
  319. defer rows.Close()
  320. for rows.Next() {
  321. var loan Loan
  322. if err := rows.Scan(
  323. &loan.Id,
  324. &loan.Amount,
  325. &loan.Term,
  326. &loan.Interest,
  327. &loan.Ltv,
  328. &loan.Dti,
  329. &loan.Hoi,
  330. &loan.Type.Id,
  331. &loan.Type.User,
  332. &loan.Type.Branch,
  333. &loan.Type.Name,
  334. )
  335. err != nil {
  336. return loans, fmt.Errorf("Loans scanning error: %v", err)
  337. }
  338. mi, err := getMi(db, loan.Id)
  339. if err != nil {
  340. return loans, err
  341. }
  342. loan.Mi = mi
  343. loans = append(loans, loan)
  344. }
  345. return loans, nil
  346. }
  347. func getBorrower(db *sql.DB, id int) (Borrower, error) {
  348. var borrower Borrower
  349. row := db.QueryRow(
  350. "SELECT * FROM borrower " +
  351. "WHERE id = ? LIMIT 1",
  352. id)
  353. if err := row.Scan(
  354. &borrower.Id,
  355. &borrower.Credit,
  356. &borrower.Income,
  357. &borrower.Num,
  358. )
  359. err != nil {
  360. return borrower, fmt.Errorf("Borrower scanning error: %v", err)
  361. }
  362. return borrower, nil
  363. }
  364. // Query Lender APIs and parse responses into MI structs
  365. func fetchMi(db *sql.DB, estimate *Estimate, pos int) []MI {
  366. var err error
  367. var loan Loan = estimate.Loans[pos]
  368. var ltv = func(l float32) string {
  369. switch {
  370. case l > 95: return "LTV97"
  371. case l > 90: return "LTV95"
  372. case l > 85: return "LTV90"
  373. default: return "LTV85"
  374. }
  375. }
  376. var term = func(t int) string {
  377. switch {
  378. case t <= 10: return "A10"
  379. case t <= 15: return "A15"
  380. case t <= 20: return "A20"
  381. case t <= 25: return "A25"
  382. case t <= 30: return "A30"
  383. default: return "A40"
  384. }
  385. }
  386. var propertyCodes = map[string]string {
  387. "Single Attached": "SFO",
  388. "Single Detached": "SFO",
  389. "Condo Lo-rise": "CON",
  390. "Condo Hi-rise": "CON",
  391. }
  392. var purposeCodes = map[string]string {
  393. "Purchase": "PUR",
  394. "Refinance": "RRT",
  395. }
  396. body, err := json.Marshal(map[string]any{
  397. "zipCode": estimate.Zip,
  398. "stateCode": "CA",
  399. "address": "",
  400. "propertyTypeCode": propertyCodes[estimate.Property],
  401. "occupancyTypeCode": "PRS",
  402. "loanPurposeCode": purposeCodes[estimate.Transaction],
  403. "loanAmount": loan.Amount,
  404. "loanToValue": ltv(loan.Ltv),
  405. "amortizationTerm": term(loan.Term),
  406. "loanTypeCode": "FXD",
  407. "duLpDecisionCode": "DAE",
  408. "loanProgramCodes": []any{},
  409. "debtToIncome": loan.Dti,
  410. "wholesaleLoan": 0,
  411. "coveragePercentageCode": "L30",
  412. "productCode": "BPM",
  413. "renewalTypeCode": "CON",
  414. "numberOfBorrowers": 1,
  415. "coBorrowerCreditScores": []any{},
  416. "borrowerCreditScore": strconv.Itoa(estimate.Borrower.Credit),
  417. "masterPolicy": nil,
  418. "selfEmployedIndicator": false,
  419. "armType": "",
  420. "userId": 44504,
  421. })
  422. if err != nil {
  423. log.Printf("Could not marshal NationalMI body: \n%v\n%v\n",
  424. bytes.NewBuffer(body), err)
  425. }
  426. req, err := http.NewRequest("POST",
  427. "https://rate-gps.nationalmi.com/rates/productRateQuote",
  428. bytes.NewBuffer(body))
  429. req.Header.Add("Content-Type", "application/json")
  430. req.AddCookie(&http.Cookie{
  431. Name: "nmirategps_email",
  432. Value: config["NationalMIEmail"]})
  433. resp, err := http.DefaultClient.Do(req)
  434. var res map[string]interface{}
  435. var result []MI
  436. if resp.StatusCode != 200 {
  437. log.Printf("the status: %v\nthe resp: %v\n the req: %v\n the body: %v\n",
  438. resp.Status, resp, req.Body, bytes.NewBuffer(body))
  439. } else {
  440. json.NewDecoder(resp.Body).Decode(&res)
  441. // estimate.Loans[pos].Mi = res
  442. // Parse res into result here
  443. }
  444. return result
  445. }
  446. func login(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  447. var id int
  448. var role string
  449. var err error
  450. r.ParseForm()
  451. row := db.QueryRow(
  452. `SELECT id, role FROM user WHERE email = ? AND password = sha2(?, 256)`,
  453. r.PostFormValue("email"), r.PostFormValue("password"))
  454. err = row.Scan(&id, &role)
  455. if err != nil {
  456. http.Error(w, "Invalid Credentials.", http.StatusUnauthorized)
  457. return
  458. }
  459. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  460. UserClaims{ Id: id, Role: role,
  461. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  462. tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
  463. if err != nil {
  464. log.Println("Token could not be signed: ", err, tokenStr)
  465. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  466. return
  467. }
  468. cookie := http.Cookie{Name: "hound",
  469. Value: tokenStr,
  470. Path: "/",
  471. Expires: time.Now().Add(time.Hour * 24)}
  472. http.SetCookie(w, &cookie)
  473. _, err = w.Write([]byte(tokenStr))
  474. if err != nil {
  475. http.Error(w,
  476. "Could not complete token write.",
  477. http.StatusInternalServerError)}
  478. }
  479. func getToken(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  480. claims, err := getClaims(r)
  481. // Will verify existing signature and expiry time
  482. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  483. UserClaims{ Id: claims.Id, Role: claims.Role,
  484. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  485. tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
  486. if err != nil {
  487. log.Println("Token could not be signed: ", err, tokenStr)
  488. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  489. return
  490. }
  491. cookie := http.Cookie{Name: "hound",
  492. Value: tokenStr,
  493. Path: "/",
  494. Expires: time.Now().Add(time.Hour * 24)}
  495. http.SetCookie(w, &cookie)
  496. _, err = w.Write([]byte(tokenStr))
  497. if err != nil {
  498. http.Error(w,
  499. "Could not complete token write.",
  500. http.StatusInternalServerError)}
  501. }
  502. func validateEstimate() {
  503. return
  504. }
  505. func getClaims(r *http.Request) (UserClaims, error) {
  506. claims := new(UserClaims)
  507. _, tokenStr, found := strings.Cut(r.Header.Get("Authorization"), " ")
  508. if !found {
  509. return *claims, errors.New("Token not found")
  510. }
  511. // Pull token payload into UserClaims
  512. _, err := jwt.ParseWithClaims(tokenStr, claims,
  513. func(token *jwt.Token) (any, error) {
  514. return []byte(config["JWT_SECRET"]), nil
  515. })
  516. if err != nil {
  517. return *claims, err
  518. }
  519. if err = claims.Valid(); err != nil {
  520. return *claims, err
  521. }
  522. return *claims, nil
  523. }
  524. func guard(r *http.Request, required int) bool {
  525. claims, err := getClaims(r)
  526. if err != nil { return false }
  527. if roles[claims.Role] < required { return false }
  528. return true
  529. }
  530. func queryUsers(db *sql.DB, id int) ( []User, error ) {
  531. var users []User
  532. var query string
  533. var rows *sql.Rows
  534. var err error
  535. query = `SELECT
  536. u.id,
  537. u.email,
  538. u.first_name,
  539. u.last_name,
  540. u.branch_id,
  541. u.country,
  542. u.title,
  543. u.status,
  544. u.verified,
  545. u.last_login,
  546. u.role
  547. FROM user u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
  548. `
  549. rows, err = db.Query(query, id)
  550. if err != nil {
  551. return users, err
  552. }
  553. defer rows.Close()
  554. for rows.Next() {
  555. var user User
  556. if err := rows.Scan(
  557. &user.Id,
  558. &user.Email,
  559. &user.FirstName,
  560. &user.LastName,
  561. &user.BranchId,
  562. &user.Country,
  563. &user.Title,
  564. &user.Status,
  565. &user.Verified,
  566. &user.LastLogin,
  567. &user.Role,
  568. )
  569. err != nil {
  570. return users, err
  571. }
  572. users = append(users, user)
  573. }
  574. // Prevents runtime panics
  575. if len(users) == 0 { return users, errors.New("User not found.") }
  576. return users, nil
  577. }
  578. func insertUser(db *sql.DB, user User) (User, error){
  579. var query string
  580. var row *sql.Row
  581. var err error
  582. var id int // Inserted user's id
  583. query = `INSERT INTO user
  584. (
  585. email,
  586. first_name,
  587. last_name,
  588. password,
  589. created,
  590. role,
  591. verified,
  592. last_login
  593. )
  594. VALUES (?, ?, ?, sha2(?, 256), NOW(), ?, ?, NOW())
  595. RETURNING id
  596. `
  597. row = db.QueryRow(query,
  598. user.Email,
  599. user.FirstName,
  600. user.LastName,
  601. user.Password,
  602. user.Role,
  603. user.Verified,
  604. )
  605. err = row.Scan(&id)
  606. if err != nil { return User{}, err }
  607. users, err := queryUsers(db, id)
  608. if err != nil { return User{}, err }
  609. return users[0], nil
  610. }
  611. func updateUser(user User, db *sql.DB) error {
  612. query := `
  613. UPDATE user
  614. SET
  615. email = CASE @e := ? WHEN '' THEN email ELSE @e END,
  616. first_name = CASE @fn := ? WHEN '' THEN first_name ELSE @fn END,
  617. last_name = CASE @ln := ? WHEN '' THEN last_name ELSE @ln END,
  618. role = CASE @r := ? WHEN '' THEN role ELSE @r END,
  619. password = CASE @p := ? WHEN '' THEN password ELSE sha2(@p, 256) END
  620. WHERE id = ?
  621. `
  622. _, err := db.Exec(query,
  623. user.Email,
  624. user.FirstName,
  625. user.LastName,
  626. user.Role,
  627. user.Password,
  628. user.Id,
  629. )
  630. return err
  631. }
  632. func getUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  633. claims, err := getClaims(r)
  634. if err != nil { w.WriteHeader(500); return }
  635. users, err := queryUsers(db, claims.Id)
  636. if err != nil { w.WriteHeader(422); return }
  637. json.NewEncoder(w).Encode(users)
  638. }
  639. func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  640. var user User
  641. err := json.NewDecoder(r.Body).Decode(&user)
  642. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  643. _, err = mail.ParseAddress(user.Email)
  644. if err != nil { http.Error(w, "Invalid email.", 422); return }
  645. if roles[user.Role] == 0 {
  646. http.Error(w, "Invalid role.", 422)
  647. }
  648. user, err = insertUser(db, user)
  649. if err != nil { http.Error(w, "Error creating user.", 422); return }
  650. json.NewEncoder(w).Encode(user)
  651. }
  652. func api(w http.ResponseWriter, r *http.Request) {
  653. var args []string
  654. p := r.URL.Path
  655. db, err := sql.Open("mysql",
  656. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
  657. config["DBUser"],
  658. config["DBPass"]))
  659. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  660. err = db.Ping()
  661. if err != nil {
  662. fmt.Println("Bad database configuration: %v\n", err)
  663. panic(err)
  664. // maybe os.Exit(1) instead
  665. }
  666. switch {
  667. case match(p, "/api/login", &args) &&
  668. r.Method == http.MethodPost:
  669. login(w, db, r)
  670. case match(p, "/api/token", &args) &&
  671. r.Method == http.MethodGet && guard(r, 1):
  672. getToken(w, db, r)
  673. case match(p, "/api/users", &args) && // Array of all users
  674. r.Method == http.MethodGet && guard(r, 3):
  675. getUsers(w, db, r)
  676. case match(p, "/api/user", &args) &&
  677. r.Method == http.MethodGet && guard(r, 1):
  678. getUser(w, db, r)
  679. case match(p, "/api/user", &args) &&
  680. r.Method == http.MethodPost &&
  681. guard(r, 3):
  682. createUser(w, db, r)
  683. case match(p, "/api/user", &args) &&
  684. r.Method == http.MethodPatch &&
  685. guard(r, 3): // For admin to modify any user
  686. patchUser(w, db, r)
  687. case match(p, "/api/user", &args) &&
  688. r.Method == http.MethodPatch &&
  689. guard(r, 2): // For employees to modify own accounts
  690. patchSelf(w, db, r)
  691. case match(p, "/api/user", &args) &&
  692. r.Method == http.MethodDelete &&
  693. guard(r, 3):
  694. deleteUser(w, db, r)
  695. }
  696. db.Close()
  697. }
  698. func route(w http.ResponseWriter, r *http.Request) {
  699. var page Page
  700. var args []string
  701. p := r.URL.Path
  702. switch {
  703. case r.Method == "GET" && match(p, "/", &args):
  704. page = pages[ "home" ]
  705. case match(p, "/terms", &args):
  706. page = pages[ "terms" ]
  707. case match(p, "/app", &args):
  708. page = pages[ "app" ]
  709. default:
  710. http.NotFound(w, r)
  711. return
  712. }
  713. page.Render(w)
  714. }
  715. func main() {
  716. files := http.FileServer(http.Dir(""))
  717. http.Handle("/assets/", files)
  718. http.HandleFunc("/api/", api)
  719. http.HandleFunc("/", route)
  720. log.Fatal(http.ListenAndServe(address, nil))
  721. }