Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

skouter.go 21 KiB

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