Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

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