Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

852 linhas
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. }