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.
 
 
 
 
 
 

964 lines
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. }