Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 
 
 

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