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.

skouter.go 29 KiB

2 jaren geleden
2 jaren geleden
2 jaren geleden
2 jaren geleden
2 jaren geleden
2 jaren geleden
1 jaar geleden
2 jaren geleden
1 jaar geleden
2 jaren geleden
2 jaren geleden
2 jaren geleden
2 jaren geleden
2 jaren geleden
1 jaar geleden
1 jaar geleden
1 jaar geleden
1 jaar geleden
1 jaar geleden
1 jaar geleden
1 jaar geleden
1 jaar geleden
1 jaar geleden
1 jaar geleden
2 jaren geleden
2 jaren geleden
1 jaar geleden
1 jaar geleden
1 jaar geleden
1 jaar geleden
1 jaar geleden
1 jaar geleden
2 jaren geleden
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360
  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 float32 `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:"type"`
  82. Amount int `json:"amount"`
  83. Amortization string `json:"amortization"`
  84. Term int `json:"term"`
  85. Ltv float32 `json:"ltv"`
  86. Dti float32 `json:"dti"`
  87. Hoi int `json:"hoi"`
  88. Tax int `json:"hoi"`
  89. Interest float32 `json:"interest"`
  90. Mi MI `json:"mi"`
  91. Fees []Fee `json:"fees"`
  92. Name string `json:"title"`
  93. }
  94. type MI struct {
  95. Type string `json:"user"`
  96. Label string `json:"label"`
  97. Lender string `json:"lender"`
  98. Rate float32 `json:"rate"`
  99. Premium float32 `json:"premium"`
  100. Upfront float32 `json:"upfront"`
  101. Monthly bool `json:"monthly"`
  102. FiveYearTotal float32 `json:"fiveYearTotal"`
  103. InitialAllInPremium float32 `json:"initialAllInPremium"`
  104. InitialAllInRate float32 `json:"initialAllInRate"`
  105. InitialAmount float32 `json:"initialAmount"`
  106. }
  107. type Result struct {
  108. User int `json:"user"`
  109. Borrower Borrower `json:"borrower"`
  110. Transaction string `json:"transaction"`
  111. Price int `json:"price"`
  112. Property string `json:"property"`
  113. Occupancy string `json:"occupancy"`
  114. Zip string `json:"zip"`
  115. Pud bool `json:"pud"`
  116. Loans []Loan `json:"loans"`
  117. }
  118. type Estimate struct {
  119. Id int `json:"id"`
  120. User int `json:"user"`
  121. Borrower Borrower `json:"borrower"`
  122. Transaction string `json:"transaction"`
  123. Price int `json:"price"`
  124. Property string `json:"property"`
  125. Occupancy string `json:"occupancy"`
  126. Zip string `json:"zip"`
  127. Pud bool `json:"pud"`
  128. Loans []Loan `json:"loans"`
  129. }
  130. var (
  131. regexen = make(map[string]*regexp.Regexp)
  132. relock sync.Mutex
  133. address = "127.0.0.1:8001"
  134. )
  135. var paths = map[string]string {
  136. "home": "home.tpl",
  137. "terms": "terms.tpl",
  138. "app": "app.tpl",
  139. }
  140. var pages = map[string]Page {
  141. "home": cache("home", "Home"),
  142. "terms": cache("terms", "Terms and Conditions"),
  143. "app": cache("app", "App"),
  144. }
  145. var roles = map[string]int{
  146. "User": 1,
  147. "Manager": 2,
  148. "Admin": 3,
  149. }
  150. // Used to validate claim in JWT token body. Checks if user id is greater than
  151. // zero and time format is valid
  152. func (c UserClaims) Valid() error {
  153. if c.Id < 1 { return errors.New("Invalid id") }
  154. t, err := time.Parse(time.UnixDate, c.Exp)
  155. if err != nil { return err }
  156. if t.Before(time.Now()) { return errors.New("Token expired.") }
  157. return err
  158. }
  159. func cache(name string, title string) Page {
  160. var p = []string{"master.tpl", paths[name]}
  161. tpl := template.Must(template.ParseFiles(p...))
  162. return Page{tpl: tpl,
  163. Title: title,
  164. Name: name,
  165. }
  166. }
  167. func (page Page) Render(w http.ResponseWriter) {
  168. err := page.tpl.Execute(w, page)
  169. if err != nil {
  170. log.Print(err)
  171. }
  172. }
  173. func match(path, pattern string, args *[]string) bool {
  174. relock.Lock()
  175. defer relock.Unlock()
  176. regex := regexen[pattern]
  177. if regex == nil {
  178. regex = regexp.MustCompile("^" + pattern + "$")
  179. regexen[pattern] = regex
  180. }
  181. matches := regex.FindStringSubmatch(path)
  182. if len(matches) <= 0 {
  183. return false
  184. }
  185. *args = matches[1:]
  186. return true
  187. }
  188. func getLoanType(
  189. db *sql.DB,
  190. user int,
  191. branch int,
  192. isUser bool) ([]LoanType, error) {
  193. var loans []LoanType
  194. // Should be changed to specify user
  195. rows, err :=
  196. db.Query(`SELECT * FROM loan_type WHERE user_id = ? AND branch_id = ? ` +
  197. "OR (user_id = 0 AND branch_id = 0)", user, branch)
  198. if err != nil {
  199. return nil, fmt.Errorf("loan_type error: %v", err)
  200. }
  201. defer rows.Close()
  202. for rows.Next() {
  203. var loan LoanType
  204. if err := rows.Scan(
  205. &loan.Id,
  206. &loan.User,
  207. &loan.Branch,
  208. &loan.Name)
  209. err != nil {
  210. log.Printf("Error occured fetching loan: %v", err)
  211. return nil, fmt.Errorf("Error occured fetching loan: %v", err)
  212. }
  213. loans = append(loans, loan)
  214. }
  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 getClaims(r *http.Request) (UserClaims, error) {
  544. claims := new(UserClaims)
  545. _, tokenStr, found := strings.Cut(r.Header.Get("Authorization"), " ")
  546. if !found {
  547. return *claims, errors.New("Token not found")
  548. }
  549. // Pull token payload into UserClaims
  550. _, err := jwt.ParseWithClaims(tokenStr, claims,
  551. func(token *jwt.Token) (any, error) {
  552. return []byte(config["JWT_SECRET"]), nil
  553. })
  554. if err != nil {
  555. return *claims, err
  556. }
  557. if err = claims.Valid(); err != nil {
  558. return *claims, err
  559. }
  560. return *claims, nil
  561. }
  562. func guard(r *http.Request, required int) bool {
  563. claims, err := getClaims(r)
  564. if err != nil { return false }
  565. if roles[claims.Role] < required { return false }
  566. return true
  567. }
  568. func queryUsers(db *sql.DB, id int) ( []User, error ) {
  569. var users []User
  570. var query string
  571. var rows *sql.Rows
  572. var err error
  573. query = `SELECT
  574. u.id,
  575. u.email,
  576. u.first_name,
  577. u.last_name,
  578. u.branch_id,
  579. u.country,
  580. u.title,
  581. u.status,
  582. u.verified,
  583. u.role
  584. FROM user u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
  585. `
  586. rows, err = db.Query(query, id)
  587. if err != nil {
  588. return users, err
  589. }
  590. defer rows.Close()
  591. for rows.Next() {
  592. var user User
  593. if err := rows.Scan(
  594. &user.Id,
  595. &user.Email,
  596. &user.FirstName,
  597. &user.LastName,
  598. &user.BranchId,
  599. &user.Country,
  600. &user.Title,
  601. &user.Status,
  602. &user.Verified,
  603. &user.Role,
  604. )
  605. err != nil {
  606. return users, err
  607. }
  608. users = append(users, user)
  609. }
  610. // Prevents runtime panics
  611. if len(users) == 0 { return users, errors.New("User not found.") }
  612. return users, nil
  613. }
  614. func insertUser(db *sql.DB, user User) (User, error){
  615. var query string
  616. var row *sql.Row
  617. var err error
  618. var id int // Inserted user's id
  619. query = `INSERT INTO user
  620. (
  621. email,
  622. first_name,
  623. last_name,
  624. password,
  625. created,
  626. role,
  627. verified,
  628. last_login
  629. )
  630. VALUES (?, ?, ?, sha2(?, 256), NOW(), ?, ?, NOW())
  631. RETURNING id
  632. `
  633. row = db.QueryRow(query,
  634. user.Email,
  635. user.FirstName,
  636. user.LastName,
  637. user.Password,
  638. user.Role,
  639. user.Verified,
  640. )
  641. err = row.Scan(&id)
  642. if err != nil { return User{}, err }
  643. users, err := queryUsers(db, id)
  644. if err != nil { return User{}, err }
  645. return users[0], nil
  646. }
  647. func updateUser(user User, db *sql.DB) error {
  648. query := `
  649. UPDATE user
  650. SET
  651. email = CASE @e := ? WHEN '' THEN email ELSE @e END,
  652. first_name = CASE @fn := ? WHEN '' THEN first_name ELSE @fn END,
  653. last_name = CASE @ln := ? WHEN '' THEN last_name ELSE @ln END,
  654. role = CASE @r := ? WHEN '' THEN role ELSE @r END,
  655. password = CASE @p := ? WHEN '' THEN password ELSE sha2(@p, 256) END
  656. WHERE id = ?
  657. `
  658. _, err := db.Exec(query,
  659. user.Email,
  660. user.FirstName,
  661. user.LastName,
  662. user.Role,
  663. user.Password,
  664. user.Id,
  665. )
  666. return err
  667. }
  668. func getUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  669. claims, err := getClaims(r)
  670. if err != nil { w.WriteHeader(500); return }
  671. users, err := queryUsers(db, claims.Id)
  672. if err != nil { w.WriteHeader(422); log.Println(err); return }
  673. json.NewEncoder(w).Encode(users)
  674. }
  675. func getUsers(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  676. users, err := queryUsers(db, 0)
  677. if err != nil {
  678. w.WriteHeader(http.StatusInternalServerError)
  679. return
  680. }
  681. json.NewEncoder(w).Encode(users)
  682. }
  683. // Updates a user using only specified values in the JSON body
  684. func patchUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  685. var user User
  686. err := json.NewDecoder(r.Body).Decode(&user)
  687. _, err = mail.ParseAddress(user.Email)
  688. if err != nil { http.Error(w, "Invalid email.", 422); return }
  689. if roles[user.Role] == 0 {
  690. http.Error(w, "Invalid role.", 422)
  691. return
  692. }
  693. err = updateUser(user, db)
  694. if err != nil { http.Error(w, "Bad form values.", 422); return }
  695. users, err := queryUsers(db, user.Id)
  696. if err != nil { http.Error(w, "Bad form values.", 422); return }
  697. json.NewEncoder(w).Encode(users[0])
  698. }
  699. // Update specified fields of the user specified in the claim
  700. func patchSelf(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  701. claim, err := getClaims(r)
  702. var user User
  703. json.NewDecoder(r.Body).Decode(&user)
  704. // First check that the target user to be updated is the same as the claim id, and
  705. // their role is unchanged.
  706. if err != nil || claim.Id != user.Id {
  707. http.Error(w, "Target user's id does not match claim.", 401)
  708. return
  709. }
  710. if claim.Role != user.Role && user.Role != "" {
  711. http.Error(w, "Administrator required to escalate role.", 401)
  712. return
  713. }
  714. patchUser(w, db, r)
  715. }
  716. func deleteUser(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 {
  720. http.Error(w, "Invalid fields.", 422)
  721. return
  722. }
  723. query := `DELETE FROM user WHERE id = ?`
  724. _, err = db.Exec(query, user.Id)
  725. if err != nil {
  726. http.Error(w, "Could not delete.", 500)
  727. }
  728. }
  729. func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  730. var user User
  731. err := json.NewDecoder(r.Body).Decode(&user)
  732. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  733. _, err = mail.ParseAddress(user.Email)
  734. if err != nil { http.Error(w, "Invalid email.", 422); return }
  735. if roles[user.Role] == 0 {
  736. http.Error(w, "Invalid role.", 422)
  737. }
  738. user, err = insertUser(db, user)
  739. if err != nil { http.Error(w, "Error creating user.", 422); return }
  740. json.NewEncoder(w).Encode(user)
  741. }
  742. func queryBorrower(db *sql.DB, id int) ( Borrower, error ) {
  743. var borrower Borrower
  744. var query string
  745. var err error
  746. query = `SELECT
  747. l.id,
  748. l.credit_score,
  749. l.num,
  750. l.monthly_income
  751. FROM borrower l WHERE l.id = ?
  752. `
  753. row := db.QueryRow(query, id)
  754. err = row.Scan(
  755. borrower.Id,
  756. borrower.Credit,
  757. borrower.Num,
  758. borrower.Income,
  759. )
  760. if err != nil {
  761. return borrower, err
  762. }
  763. return borrower, nil
  764. }
  765. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  766. func queryLoan(db *sql.DB, e int, id int) ( []Loan, error ) {
  767. var loans []Loan
  768. var query string
  769. var rows *sql.Rows
  770. var err error
  771. fmt.Println(e, id)
  772. query = `SELECT
  773. l.id,
  774. l.estimate_id,
  775. l.amount,
  776. l.term,
  777. l.interest,
  778. l.ltv,
  779. l.dti,
  780. l.hoi,
  781. l.tax,
  782. l.name
  783. FROM loan l WHERE l.id = CASE @e := ? WHEN 0 THEN l.id ELSE @e END AND
  784. l.estimate_id = ?
  785. `
  786. rows, err = db.Query(query, id, e)
  787. if err != nil {
  788. return loans, err
  789. }
  790. defer rows.Close()
  791. for rows.Next() {
  792. var loan Loan
  793. if err := rows.Scan(
  794. &loan.Id,
  795. &loan.EstimateId,
  796. &loan.Amount,
  797. &loan.Term,
  798. &loan.Interest,
  799. &loan.Ltv,
  800. &loan.Dti,
  801. &loan.Hoi,
  802. &loan.Tax,
  803. &loan.Name,
  804. )
  805. err != nil {
  806. return loans, err
  807. }
  808. loans = append(loans, loan)
  809. }
  810. // Prevents runtime panics
  811. if len(loans) == 0 { return loans, errors.New("Loan not found.") }
  812. return loans, nil
  813. }
  814. func queryEstimate(db *sql.DB, id int) ( []Estimate, error ) {
  815. var estimates []Estimate
  816. var query string
  817. var rows *sql.Rows
  818. var err error
  819. query = `SELECT
  820. u.id,
  821. u.user_id,
  822. u.borrower_id,
  823. u.transaction,
  824. u.price,
  825. u.property,
  826. u.occupancy,
  827. u.zip,
  828. u.pud
  829. FROM estimate u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
  830. `
  831. rows, err = db.Query(query, id)
  832. if err != nil {
  833. return estimates, err
  834. }
  835. defer rows.Close()
  836. for rows.Next() {
  837. var estimate Estimate
  838. if err := rows.Scan(
  839. &estimate.Id,
  840. &estimate.User,
  841. &estimate.Borrower.Id,
  842. &estimate.Transaction,
  843. &estimate.Price,
  844. &estimate.Property,
  845. &estimate.Occupancy,
  846. &estimate.Zip,
  847. &estimate.Pud,
  848. )
  849. err != nil {
  850. return estimates, err
  851. }
  852. estimates = append(estimates, estimate)
  853. }
  854. // Prevents runtime panics
  855. if len(estimates) == 0 { return estimates, errors.New("Estimate not found.") }
  856. for _, e := range estimates {
  857. fmt.Println("here's the estimate ID:", e.Id)
  858. e.Loans, err = queryLoan(db, e.Id, 0)
  859. if err != nil { return estimates, err }
  860. }
  861. return estimates, nil
  862. }
  863. // Accepts a borrower struct and returns the id of the inserted borrower and
  864. // any related error.
  865. func insertBorrower(db *sql.DB, borrower Borrower) (int, error) {
  866. var query string
  867. var row *sql.Row
  868. var err error
  869. var id int // Inserted loan's id
  870. query = `INSERT INTO borrower
  871. (
  872. credit_score,
  873. monthly_income,
  874. num
  875. )
  876. VALUES (?, ?, ?)
  877. RETURNING id
  878. `
  879. row = db.QueryRow(query,
  880. borrower.Credit,
  881. borrower.Income,
  882. borrower.Num,
  883. )
  884. err = row.Scan(&id)
  885. if err != nil { return 0, err }
  886. return id, nil
  887. }
  888. func insertLoan(db *sql.DB, loan Loan) (Loan, error){
  889. var query string
  890. var row *sql.Row
  891. var err error
  892. var id int // Inserted loan's id
  893. query = `INSERT INTO loan
  894. (
  895. estimate_id,
  896. type_id,
  897. amount,
  898. term,
  899. interest,
  900. ltv,
  901. dti,
  902. hoi,
  903. tax,
  904. name
  905. )
  906. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  907. RETURNING id
  908. `
  909. row = db.QueryRow(query,
  910. loan.EstimateId,
  911. loan.Type.Id,
  912. loan.Amount,
  913. loan.Term,
  914. loan.Interest,
  915. loan.Ltv,
  916. loan.Dti,
  917. loan.Hoi,
  918. loan.Tax,
  919. loan.Name,
  920. )
  921. err = row.Scan(&id)
  922. if err != nil { return Loan{}, err }
  923. loans, err := queryLoan(db, id, 0)
  924. if err != nil { return Loan{}, err }
  925. return loans[0], nil
  926. }
  927. func insertEstimate(db *sql.DB, estimate Estimate) (Estimate, error){
  928. var query string
  929. var row *sql.Row
  930. var err error
  931. // var id int // Inserted estimate's id
  932. estimate.Borrower.Id, err = insertBorrower(db, estimate.Borrower)
  933. if err != nil { return Estimate{}, err }
  934. query = `INSERT INTO estimate
  935. (
  936. user_id,
  937. borrower_id,
  938. transaction,
  939. price,
  940. property,
  941. occupancy,
  942. zip,
  943. pud
  944. )
  945. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  946. RETURNING id
  947. `
  948. row = db.QueryRow(query,
  949. estimate.User,
  950. estimate.Borrower.Id,
  951. estimate.Transaction,
  952. estimate.Price,
  953. estimate.Property,
  954. estimate.Occupancy,
  955. estimate.Zip,
  956. estimate.Pud,
  957. )
  958. err = row.Scan(&estimate.Id)
  959. if err != nil { return Estimate{}, err }
  960. for _, l := range estimate.Loans {
  961. l.EstimateId = estimate.Id
  962. _, err = insertLoan(db, l)
  963. if err != nil { return estimate, err }
  964. }
  965. estimates, err := queryEstimate(db, estimate.Id)
  966. if err != nil { return Estimate{}, err }
  967. return estimates[0], nil
  968. }
  969. func createEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  970. var estimate Estimate
  971. err := json.NewDecoder(r.Body).Decode(&estimate)
  972. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  973. estimate, err = insertEstimate(db, estimate)
  974. if err != nil { http.Error(w, err.Error(), 422); return }
  975. json.NewEncoder(w).Encode(estimate)
  976. }
  977. func checkConventional(l Loan, b Borrower) error {
  978. if b.Credit < 620 {
  979. return errors.New("Credit score too low for conventional loan")
  980. }
  981. // Buyer needs to put down 20% to avoid mortgage insurance
  982. if (l.Ltv > 80 && l.Mi.Rate == 0) {
  983. return errors.New(fmt.Sprintln(
  984. l.Name,
  985. "down payment must be 20% to avoid insurance",
  986. ))
  987. }
  988. return nil
  989. }
  990. func checkFHA(l Loan, b Borrower) error {
  991. if b.Credit < 500 {
  992. return errors.New("Credit score too low for FHA loan")
  993. }
  994. if (l.Ltv > 96.5) {
  995. return errors.New("FHA down payment must be at least 3.5%")
  996. }
  997. if (b.Credit < 580 && l.Ltv > 90) {
  998. return errors.New("FHA down payment must be at least 10%")
  999. }
  1000. // Debt-to-income must be below 45% if credit score is below 580.
  1001. if (b.Credit < 580 && l.Dti > 45) {
  1002. return errors.New(fmt.Sprintln(
  1003. l.Name, "debt to income is too high for credit score.",
  1004. ))
  1005. }
  1006. return nil
  1007. }
  1008. // Loan option for veterans with no set rules
  1009. func checkVA(l Loan, b Borrower) error {
  1010. return nil
  1011. }
  1012. // Loan option for residents of rural areas with no set rules
  1013. func checkUSDA(l Loan, b Borrower) error {
  1014. return nil
  1015. }
  1016. // Should also check loan amount limit maybe with an API.
  1017. func checkEstimate(e Estimate) error {
  1018. if e.Property == "" { return errors.New("Empty property type") }
  1019. if e.Price == 0 { return errors.New("Empty property price") }
  1020. if e.Borrower.Num == 0 {
  1021. return errors.New("Missing number of borrowers")
  1022. }
  1023. if e.Borrower.Credit == 0 {
  1024. return errors.New("Missing borrower credit score")
  1025. }
  1026. if e.Borrower.Income == 0 {
  1027. return errors.New("Missing borrower credit income")
  1028. }
  1029. for _, l := range e.Loans {
  1030. if l.Amount == 0 {
  1031. return errors.New(fmt.Sprintln(l.Name, "loan amount cannot be zero"))
  1032. }
  1033. if l.Term == 0 {
  1034. return errors.New(fmt.Sprintln(l.Name, "loan term cannot be zero"))
  1035. }
  1036. if l.Interest == 0 {
  1037. return errors.New(fmt.Sprintln(l.Name, "loan interest cannot be zero"))
  1038. }
  1039. // Can be used to check rules for specific loan types
  1040. var err error
  1041. switch l.Type.Id {
  1042. case 1:
  1043. err = checkConventional(l, e.Borrower)
  1044. case 2:
  1045. err = checkFHA(l, e.Borrower)
  1046. case 3:
  1047. err = checkVA(l, e.Borrower)
  1048. case 4:
  1049. err = checkUSDA(l, e.Borrower)
  1050. default:
  1051. err = errors.New("Invalid loan type")
  1052. }
  1053. if err != nil { return err }
  1054. }
  1055. return nil
  1056. }
  1057. func validateEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1058. var estimate Estimate
  1059. err := json.NewDecoder(r.Body).Decode(&estimate)
  1060. if err != nil { http.Error(w, err.Error(), 422); return }
  1061. err = checkEstimate(estimate)
  1062. if err != nil { http.Error(w, err.Error(), 406); return }
  1063. }
  1064. func api(w http.ResponseWriter, r *http.Request) {
  1065. var args []string
  1066. p := r.URL.Path
  1067. db, err := sql.Open("mysql",
  1068. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
  1069. config["DBUser"],
  1070. config["DBPass"]))
  1071. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  1072. err = db.Ping()
  1073. if err != nil {
  1074. fmt.Println("Bad database configuration: %v\n", err)
  1075. panic(err)
  1076. // maybe os.Exit(1) instead
  1077. }
  1078. switch {
  1079. case match(p, "/api/login", &args) &&
  1080. r.Method == http.MethodPost:
  1081. login(w, db, r)
  1082. case match(p, "/api/token", &args) &&
  1083. r.Method == http.MethodGet && guard(r, 1):
  1084. getToken(w, db, r)
  1085. case match(p, "/api/users", &args) && // Array of all users
  1086. r.Method == http.MethodGet && guard(r, 3):
  1087. getUsers(w, db, r)
  1088. case match(p, "/api/user", &args) &&
  1089. r.Method == http.MethodGet && guard(r, 1):
  1090. getUser(w, db, r)
  1091. case match(p, "/api/user", &args) &&
  1092. r.Method == http.MethodPost &&
  1093. guard(r, 3):
  1094. createUser(w, db, r)
  1095. case match(p, "/api/user", &args) &&
  1096. r.Method == http.MethodPatch &&
  1097. guard(r, 3): // For admin to modify any user
  1098. patchUser(w, db, r)
  1099. case match(p, "/api/user", &args) &&
  1100. r.Method == http.MethodPatch &&
  1101. guard(r, 2): // For employees to modify own accounts
  1102. patchSelf(w, db, r)
  1103. case match(p, "/api/user", &args) &&
  1104. r.Method == http.MethodDelete &&
  1105. guard(r, 3):
  1106. deleteUser(w, db, r)
  1107. case match(p, "/api/fees", &args) &&
  1108. r.Method == http.MethodGet &&
  1109. guard(r, 1):
  1110. getFeesTemp(w, db, r)
  1111. case match(p, "/api/estimate", &args) &&
  1112. r.Method == http.MethodPost &&
  1113. guard(r, 1):
  1114. createEstimate(w, db, r)
  1115. case match(p, "/api/estimate/validate", &args) &&
  1116. r.Method == http.MethodPost &&
  1117. guard(r, 1):
  1118. validateEstimate(w, db, r)
  1119. default:
  1120. http.Error(w, "Invalid route or token", 404)
  1121. }
  1122. db.Close()
  1123. }
  1124. func route(w http.ResponseWriter, r *http.Request) {
  1125. var page Page
  1126. var args []string
  1127. p := r.URL.Path
  1128. switch {
  1129. case r.Method == "GET" && match(p, "/", &args):
  1130. page = pages[ "home" ]
  1131. case match(p, "/terms", &args):
  1132. page = pages[ "terms" ]
  1133. case match(p, "/app", &args):
  1134. page = pages[ "app" ]
  1135. default:
  1136. http.NotFound(w, r)
  1137. return
  1138. }
  1139. page.Render(w)
  1140. }
  1141. func main() {
  1142. files := http.FileServer(http.Dir(""))
  1143. http.Handle("/assets/", files)
  1144. http.HandleFunc("/api/", api)
  1145. http.HandleFunc("/", route)
  1146. log.Fatal(http.ListenAndServe(address, nil))
  1147. }