Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

vor 2 Jahren
vor 2 Jahren
vor 2 Jahren
vor 2 Jahren
vor 2 Jahren
vor 2 Jahren
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808
  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. "math"
  19. "io"
  20. // pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
  21. "github.com/golang-jwt/jwt/v4"
  22. )
  23. type User struct {
  24. Id int `json:"id"`
  25. Email string `json:"email"`
  26. FirstName string `json:"firstName"`
  27. LastName string `json:"lastName"`
  28. BranchId int `json:"branchId"`
  29. Status string `json:"status"`
  30. Country string `json:"country"`
  31. Title string `json:"title"`
  32. Verified bool `json:"verified"`
  33. Role string `json:"role"`
  34. Password string `json:"password,omitempty"`
  35. }
  36. type UserClaims struct {
  37. Id int `json:"id"`
  38. Role string `json:"role"`
  39. Exp string `json:"exp"`
  40. }
  41. type Page struct {
  42. tpl *template.Template
  43. Title string
  44. Name string
  45. }
  46. type Borrower struct {
  47. Id int `json:"id"`
  48. Credit int `json:"credit"`
  49. Income int `json:"income"`
  50. Num int `json:"num"`
  51. }
  52. type FeeTemplate struct {
  53. Id int `json:"id"`
  54. User int `json:"user"`
  55. Branch int `json:"branch"`
  56. Amount int `json:"amount"`
  57. Perc float32 `json:"perc"`
  58. Type string `json:"type"`
  59. Notes string `json:"notes"`
  60. Name string `json:"name"`
  61. Category string `json:"category"`
  62. Auto bool `json:"auto"`
  63. }
  64. type Fee struct {
  65. Id int `json:"id"`
  66. LoanId int `json:"loan_id"`
  67. Amount int `json:"amount"`
  68. Perc float32 `json:"perc"`
  69. Type string `json:"type"`
  70. Notes string `json:"notes"`
  71. Name string `json:"name"`
  72. Category string `json:"category"`
  73. }
  74. type LoanType struct {
  75. Id int `json:"id"`
  76. User int `json:"user"`
  77. Branch int `json:"branch"`
  78. Name string `json:"name"`
  79. }
  80. type Loan struct {
  81. Id int `json:"id"`
  82. EstimateId int `json:"estimateId"`
  83. Type LoanType `json:"type"`
  84. Amount int `json:"amount"`
  85. Amortization string `json:"amortization"`
  86. Term int `json:"term"`
  87. Ltv float32 `json:"ltv"`
  88. Dti float32 `json:"dti"`
  89. Hoi int `json:"hoi"`
  90. Hazard int `json:"hazard"`
  91. Tax int `json:"tax"`
  92. Interest float32 `json:"interest"`
  93. Mi MI `json:"mi"`
  94. Fees []Fee `json:"fees"`
  95. Name string `json:"title"`
  96. }
  97. type MI struct {
  98. Type string `json:"user"`
  99. Label string `json:"label"`
  100. Lender string `json:"lender"`
  101. Rate float32 `json:"rate"`
  102. Premium float32 `json:"premium"`
  103. Upfront float32 `json:"upfront"`
  104. Monthly bool `json:"monthly"`
  105. FiveYearTotal float32 `json:"fiveYearTotal"`
  106. InitialAllInPremium float32 `json:"initialAllInPremium"`
  107. InitialAllInRate float32 `json:"initialAllInRate"`
  108. InitialAmount float32 `json:"initialAmount"`
  109. }
  110. type Result struct {
  111. Id int `json:"id"`
  112. LoanId int `json:"loanId"`
  113. LoanPayment int `json:"loanPayment"`
  114. TotalMonthly int `json:"totalMonthly"`
  115. TotalFees int `json:"totalFees"`
  116. TotalCredits int `json:"totalCredits"`
  117. CashToClose int `json:"cashToClose"`
  118. }
  119. type Estimate struct {
  120. Id int `json:"id"`
  121. User int `json:"user"`
  122. Borrower Borrower `json:"borrower"`
  123. Transaction string `json:"transaction"`
  124. Price int `json:"price"`
  125. Property string `json:"property"`
  126. Occupancy string `json:"occupancy"`
  127. Zip string `json:"zip"`
  128. Pud bool `json:"pud"`
  129. Loans []Loan `json:"loans"`
  130. Results []Result `json:"results"`
  131. }
  132. var (
  133. regexen = make(map[string]*regexp.Regexp)
  134. relock sync.Mutex
  135. address = "127.0.0.1:8001"
  136. )
  137. var paths = map[string]string {
  138. "home": "views/home.tpl",
  139. "terms": "views/terms.tpl",
  140. "app": "views/app.tpl",
  141. "test": "views/test.tpl",
  142. }
  143. var pages = map[string]Page {
  144. "home": cache("home", "Home"),
  145. "terms": cache("terms", "Terms and Conditions"),
  146. "test": cache("test", "PDF test"),
  147. "app": cache("app", "App"),
  148. }
  149. var roles = map[string]int{
  150. "User": 1,
  151. "Manager": 2,
  152. "Admin": 3,
  153. }
  154. // Used to validate claim in JWT token body. Checks if user id is greater than
  155. // zero and time format is valid
  156. func (c UserClaims) Valid() error {
  157. if c.Id < 1 { return errors.New("Invalid id") }
  158. t, err := time.Parse(time.UnixDate, c.Exp)
  159. if err != nil { return err }
  160. if t.Before(time.Now()) { return errors.New("Token expired.") }
  161. return err
  162. }
  163. func cache(name string, title string) Page {
  164. var p = []string{"views/master.tpl", paths[name]}
  165. tpl := template.Must(template.ParseFiles(p...))
  166. return Page{tpl: tpl,
  167. Title: title,
  168. Name: name,
  169. }
  170. }
  171. func (page Page) Render(w http.ResponseWriter) {
  172. err := page.tpl.Execute(w, page)
  173. if err != nil {
  174. log.Print(err)
  175. }
  176. }
  177. func match(path, pattern string, args *[]string) bool {
  178. relock.Lock()
  179. defer relock.Unlock()
  180. regex := regexen[pattern]
  181. if regex == nil {
  182. regex = regexp.MustCompile("^" + pattern + "$")
  183. regexen[pattern] = regex
  184. }
  185. matches := regex.FindStringSubmatch(path)
  186. if len(matches) <= 0 {
  187. return false
  188. }
  189. *args = matches[1:]
  190. return true
  191. }
  192. func makeResults(estimate Estimate) ([]Result) {
  193. var results []Result
  194. amortize := func(principle float64, rate float64, periods float64) int {
  195. exp := math.Pow(1+rate, periods)
  196. return int(principle * rate * exp / (exp - 1))
  197. }
  198. for _, loan := range estimate.Loans {
  199. var result Result
  200. // Monthly payments use amortized loan payment formula plus monthly MI,
  201. // plus all other recurring fees
  202. result.LoanPayment = amortize(float64(loan.Amount),
  203. float64(loan.Interest / 100 / 12),
  204. float64(loan.Term * 12))
  205. result.TotalMonthly = result.LoanPayment + loan.Hoi + loan.Tax + loan.Hazard
  206. if loan.Mi.Monthly {
  207. result.TotalMonthly = result.TotalMonthly +
  208. int(loan.Mi.Rate/100*float32(loan.Amount))
  209. }
  210. for i := range loan.Fees {
  211. if loan.Fees[i].Amount > 0 {
  212. result.TotalFees = result.TotalFees + loan.Fees[i].Amount
  213. } else {
  214. result.TotalCredits = result.TotalCredits + loan.Fees[i].Amount
  215. }
  216. }
  217. result.CashToClose =
  218. result.TotalFees + result.TotalCredits + (estimate.Price - loan.Amount)
  219. result.LoanId = loan.Id
  220. results = append(results, result)
  221. }
  222. return results
  223. }
  224. func summarize(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  225. var estimate Estimate
  226. err := json.NewDecoder(r.Body).Decode(&estimate)
  227. if err != nil { http.Error(w, "Invalid estimate.", 422); return }
  228. results := makeResults(estimate)
  229. json.NewEncoder(w).Encode(results)
  230. }
  231. func getLoanType( db *sql.DB, id int) (LoanType, error) {
  232. types, err := getLoanTypes(db, id, 0, 0)
  233. if err != nil { return LoanType{Id: id}, err }
  234. if len(types) == 0 {
  235. return LoanType{Id: id}, errors.New("No type with that id")
  236. }
  237. return types[0], nil
  238. }
  239. func getLoanTypes( db *sql.DB, id int, user int, branch int ) (
  240. []LoanType, error) {
  241. var loans []LoanType
  242. var query = `SELECT
  243. id,
  244. user_id,
  245. branch_id,
  246. name
  247. FROM loan_type WHERE loan_type.id = CASE @e := ? WHEN 0 THEN id ELSE @e END
  248. OR
  249. loan_type.user_id = CASE @e := ? WHEN 0 THEN id ELSE @e END
  250. OR
  251. loan_type.branch_id = CASE @e := ? WHEN 0 THEN id ELSE @e END`
  252. // Should be changed to specify user
  253. rows, err := db.Query(query, id, user, branch)
  254. if err != nil {
  255. return nil, fmt.Errorf("loan_type error: %v", err)
  256. }
  257. defer rows.Close()
  258. for rows.Next() {
  259. var loan LoanType
  260. if err := rows.Scan(
  261. &loan.Id,
  262. &loan.User,
  263. &loan.Branch,
  264. &loan.Name)
  265. err != nil {
  266. log.Printf("Error occured fetching loan: %v", err)
  267. return nil, fmt.Errorf("Error occured fetching loan: %v", err)
  268. }
  269. loans = append(loans, loan)
  270. }
  271. return loans, nil
  272. }
  273. func getFees(db *sql.DB, loan int) ([]Fee, error) {
  274. var fees []Fee
  275. query := `SELECT id, loan_id, amount, perc, type, notes, name, category
  276. FROM fee
  277. WHERE loan_id = ?`
  278. rows, err := db.Query(query, loan)
  279. if err != nil {
  280. return nil, fmt.Errorf("Fee query error %v", err)
  281. }
  282. defer rows.Close()
  283. for rows.Next() {
  284. var fee Fee
  285. if err := rows.Scan(
  286. &fee.Id,
  287. &fee.LoanId,
  288. &fee.Amount,
  289. &fee.Perc,
  290. &fee.Type,
  291. &fee.Notes,
  292. &fee.Name,
  293. &fee.Category,
  294. )
  295. err != nil {
  296. return nil, fmt.Errorf("Fees scanning error: %v", err)
  297. }
  298. fees = append(fees, fee)
  299. }
  300. return fees, nil
  301. }
  302. func fetchFeesTemp(db *sql.DB, user int, branch int) ([]FeeTemplate, error) {
  303. var fees []FeeTemplate
  304. query := `SELECT
  305. id, user_id, COALESCE(branch_id, 0), amount, perc, type, notes, name,
  306. category, auto
  307. FROM fee_template
  308. WHERE user_id = ? OR branch_id = ?
  309. `
  310. rows, err := db.Query(query, user, branch)
  311. if err != nil {
  312. return nil, fmt.Errorf("Fee template query error %v", err)
  313. }
  314. defer rows.Close()
  315. for rows.Next() {
  316. var fee FeeTemplate
  317. if err := rows.Scan(
  318. &fee.Id,
  319. &fee.User,
  320. &fee.Branch,
  321. &fee.Amount,
  322. &fee.Perc,
  323. &fee.Type,
  324. &fee.Notes,
  325. &fee.Name,
  326. &fee.Category,
  327. &fee.Auto)
  328. err != nil {
  329. return nil, fmt.Errorf("FeesTemplate scanning error: %v", err)
  330. }
  331. fees = append(fees, fee)
  332. }
  333. return fees, nil
  334. }
  335. // Fetch fees from the database
  336. func getFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  337. var fees []FeeTemplate
  338. claims, err := getClaims(r)
  339. if err != nil { w.WriteHeader(500); return }
  340. users, err := queryUsers(db, claims.Id)
  341. if err != nil { w.WriteHeader(422); return }
  342. fees, err = fetchFeesTemp(db, claims.Id, users[0].BranchId)
  343. json.NewEncoder(w).Encode(fees)
  344. }
  345. // Fetch fees from the database
  346. func createFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  347. var fee FeeTemplate
  348. var query string
  349. var row *sql.Row
  350. var err error
  351. claims, err := getClaims(r)
  352. // var id int // Inserted estimate's id
  353. err = json.NewDecoder(r.Body).Decode(&fee)
  354. if err != nil { w.WriteHeader(422); return }
  355. query = `INSERT INTO fee_template
  356. (
  357. user_id,
  358. branch_id,
  359. amount,
  360. perc,
  361. type,
  362. notes,
  363. name,
  364. auto
  365. )
  366. VALUES (?, NULL, ?, ?, ?, ?, ?, ?)
  367. RETURNING id
  368. `
  369. row = db.QueryRow(query,
  370. claims.Id,
  371. fee.Amount,
  372. fee.Perc,
  373. fee.Type,
  374. fee.Notes,
  375. fee.Name,
  376. fee.Auto,
  377. )
  378. err = row.Scan(&fee.Id)
  379. if err != nil { w.WriteHeader(500); return }
  380. json.NewEncoder(w).Encode(fee)
  381. }
  382. // Fetch fees from the database
  383. func deleteFeeTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  384. var fee FeeTemplate
  385. var query string
  386. var err error
  387. // claims, err := getClaims(r)
  388. // var id int // Inserted estimate's id
  389. err = json.NewDecoder(r.Body).Decode(&fee)
  390. if err != nil { w.WriteHeader(422); return }
  391. query = `DELETE FROM fee_template WHERE id = ?`
  392. _, err = db.Exec(query, fee.Id)
  393. if err != nil { w.WriteHeader(500); return }
  394. }
  395. func getMi(db *sql.DB, loan int) (MI, error) {
  396. var mi MI
  397. query := `SELECT
  398. type, label, lender, rate, premium, upfront, five_year_total,
  399. initial_premium, initial_rate, initial_amount
  400. FROM mi WHERE loan_id = ?`
  401. rows, err := db.Query(query, loan)
  402. if err != nil { return mi, err }
  403. defer rows.Close()
  404. if (!rows.Next()) { return mi, nil }
  405. if err := rows.Scan(
  406. &mi.Type,
  407. &mi.Label,
  408. &mi.Lender,
  409. &mi.Rate,
  410. &mi.Premium,
  411. &mi.Upfront,
  412. &mi.FiveYearTotal,
  413. &mi.InitialAllInPremium,
  414. &mi.InitialAllInRate,
  415. &mi.InitialAmount,
  416. )
  417. err != nil {
  418. return mi, err
  419. }
  420. return mi, nil
  421. }
  422. func getBorrower(db *sql.DB, id int) (Borrower, error) {
  423. var borrower Borrower
  424. row := db.QueryRow(
  425. "SELECT * FROM borrower " +
  426. "WHERE id = ? LIMIT 1",
  427. id)
  428. if err := row.Scan(
  429. &borrower.Id,
  430. &borrower.Credit,
  431. &borrower.Income,
  432. &borrower.Num,
  433. )
  434. err != nil {
  435. return borrower, fmt.Errorf("Borrower scanning error: %v", err)
  436. }
  437. return borrower, nil
  438. }
  439. // Query Lender APIs and parse responses into MI structs
  440. func fetchMi(db *sql.DB, estimate *Estimate, pos int) []MI {
  441. var err error
  442. var loan Loan = estimate.Loans[pos]
  443. var ltv = func(l float32) string {
  444. switch {
  445. case l > 95: return "LTV97"
  446. case l > 90: return "LTV95"
  447. case l > 85: return "LTV90"
  448. default: return "LTV85"
  449. }
  450. }
  451. var term = func(t int) string {
  452. switch {
  453. case t <= 10: return "A10"
  454. case t <= 15: return "A15"
  455. case t <= 20: return "A20"
  456. case t <= 25: return "A25"
  457. case t <= 30: return "A30"
  458. default: return "A40"
  459. }
  460. }
  461. var propertyCodes = map[string]string {
  462. "Single Attached": "SFO",
  463. "Single Detached": "SFO",
  464. "Condo Lo-rise": "CON",
  465. "Condo Hi-rise": "CON",
  466. }
  467. var purposeCodes = map[string]string {
  468. "Purchase": "PUR",
  469. "Refinance": "RRT",
  470. }
  471. body, err := json.Marshal(map[string]any{
  472. "zipCode": estimate.Zip,
  473. "stateCode": "CA",
  474. "address": "",
  475. "propertyTypeCode": propertyCodes[estimate.Property],
  476. "occupancyTypeCode": "PRS",
  477. "loanPurposeCode": purposeCodes[estimate.Transaction],
  478. "loanAmount": loan.Amount,
  479. "loanToValue": ltv(loan.Ltv),
  480. "amortizationTerm": term(loan.Term),
  481. "loanTypeCode": "FXD",
  482. "duLpDecisionCode": "DAE",
  483. "loanProgramCodes": []any{},
  484. "debtToIncome": loan.Dti,
  485. "wholesaleLoan": 0,
  486. "coveragePercentageCode": "L30",
  487. "productCode": "BPM",
  488. "renewalTypeCode": "CON",
  489. "numberOfBorrowers": 1,
  490. "coBorrowerCreditScores": []any{},
  491. "borrowerCreditScore": strconv.Itoa(estimate.Borrower.Credit),
  492. "masterPolicy": nil,
  493. "selfEmployedIndicator": false,
  494. "armType": "",
  495. "userId": 44504,
  496. })
  497. if err != nil {
  498. log.Printf("Could not marshal NationalMI body: \n%v\n%v\n",
  499. bytes.NewBuffer(body), err)
  500. }
  501. req, err := http.NewRequest("POST",
  502. "https://rate-gps.nationalmi.com/rates/productRateQuote",
  503. bytes.NewBuffer(body))
  504. req.Header.Add("Content-Type", "application/json")
  505. req.AddCookie(&http.Cookie{
  506. Name: "nmirategps_email",
  507. Value: config["NationalMIEmail"]})
  508. resp, err := http.DefaultClient.Do(req)
  509. var res map[string]interface{}
  510. var result []MI
  511. if resp.StatusCode != 200 {
  512. log.Printf("the status: %v\nthe resp: %v\n the req: %v\n the body: %v\n",
  513. resp.Status, resp, req.Body, bytes.NewBuffer(body))
  514. } else {
  515. json.NewDecoder(resp.Body).Decode(&res)
  516. // estimate.Loans[pos].Mi = res
  517. // Parse res into result here
  518. }
  519. return result
  520. }
  521. // Make comparison PDF
  522. func generatePDF(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  523. }
  524. func login(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  525. var id int
  526. var role string
  527. var err error
  528. var user User
  529. json.NewDecoder(r.Body).Decode(&user)
  530. row := db.QueryRow(
  531. `SELECT id, role FROM user WHERE email = ? AND password = sha2(?, 256)`,
  532. user.Email, user.Password,
  533. )
  534. err = row.Scan(&id, &role)
  535. if err != nil {
  536. http.Error(w, "Invalid Credentials.", http.StatusUnauthorized)
  537. return
  538. }
  539. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  540. UserClaims{ Id: id, Role: role,
  541. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  542. tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
  543. if err != nil {
  544. log.Println("Token could not be signed: ", err, tokenStr)
  545. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  546. return
  547. }
  548. cookie := http.Cookie{Name: "skouter",
  549. Value: tokenStr,
  550. Path: "/",
  551. Expires: time.Now().Add(time.Hour * 24)}
  552. http.SetCookie(w, &cookie)
  553. _, err = w.Write([]byte(tokenStr))
  554. if err != nil {
  555. http.Error(w,
  556. "Could not complete token write.",
  557. http.StatusInternalServerError)}
  558. }
  559. func getToken(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  560. claims, err := getClaims(r)
  561. // Will verify existing signature and expiry time
  562. token := jwt.NewWithClaims(jwt.SigningMethodHS256,
  563. UserClaims{ Id: claims.Id, Role: claims.Role,
  564. Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})
  565. tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
  566. if err != nil {
  567. log.Println("Token could not be signed: ", err, tokenStr)
  568. http.Error(w, "Token generation error.", http.StatusInternalServerError)
  569. return
  570. }
  571. cookie := http.Cookie{Name: "skouter",
  572. Value: tokenStr,
  573. Path: "/",
  574. Expires: time.Now().Add(time.Hour * 24)}
  575. http.SetCookie(w, &cookie)
  576. _, err = w.Write([]byte(tokenStr))
  577. if err != nil {
  578. http.Error(w,
  579. "Could not complete token write.",
  580. http.StatusInternalServerError)}
  581. }
  582. func getClaims(r *http.Request) (UserClaims, error) {
  583. claims := new(UserClaims)
  584. _, tokenStr, found := strings.Cut(r.Header.Get("Authorization"), " ")
  585. if !found {
  586. return *claims, errors.New("Token not found")
  587. }
  588. // Pull token payload into UserClaims
  589. _, err := jwt.ParseWithClaims(tokenStr, claims,
  590. func(token *jwt.Token) (any, error) {
  591. return []byte(config["JWT_SECRET"]), nil
  592. })
  593. if err != nil {
  594. return *claims, err
  595. }
  596. if err = claims.Valid(); err != nil {
  597. return *claims, err
  598. }
  599. return *claims, nil
  600. }
  601. func guard(r *http.Request, required int) bool {
  602. claims, err := getClaims(r)
  603. if err != nil { return false }
  604. if roles[claims.Role] < required { return false }
  605. return true
  606. }
  607. func queryUsers(db *sql.DB, id int) ( []User, error ) {
  608. var users []User
  609. var query string
  610. var rows *sql.Rows
  611. var err error
  612. query = `SELECT
  613. u.id,
  614. u.email,
  615. u.first_name,
  616. u.last_name,
  617. u.branch_id,
  618. u.country,
  619. u.title,
  620. u.status,
  621. u.verified,
  622. u.role
  623. FROM user u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
  624. `
  625. rows, err = db.Query(query, id)
  626. if err != nil {
  627. return users, err
  628. }
  629. defer rows.Close()
  630. for rows.Next() {
  631. var user User
  632. if err := rows.Scan(
  633. &user.Id,
  634. &user.Email,
  635. &user.FirstName,
  636. &user.LastName,
  637. &user.BranchId,
  638. &user.Country,
  639. &user.Title,
  640. &user.Status,
  641. &user.Verified,
  642. &user.Role,
  643. )
  644. err != nil {
  645. return users, err
  646. }
  647. users = append(users, user)
  648. }
  649. // Prevents runtime panics
  650. if len(users) == 0 { return users, errors.New("User not found.") }
  651. return users, nil
  652. }
  653. func insertResults(db *sql.DB, results []Result) (error){
  654. var query string
  655. var row *sql.Row
  656. var err error
  657. query = `INSERT INTO estimate_result
  658. (
  659. loan_id,
  660. loan_payment,
  661. total_monthly,
  662. total_fees,
  663. total_credits,
  664. cash_to_close
  665. )
  666. VALUES (?, ?, ?, ?, ?, ?, ?)
  667. RETURNING id
  668. `
  669. for i := range results {
  670. db.QueryRow(query,
  671. results[i].LoanId,
  672. results[i].LoanPayment,
  673. results[i].TotalMonthly,
  674. results[i].TotalFees,
  675. results[i].TotalCredits,
  676. results[i].CashToClose,
  677. )
  678. err = row.Scan(&results[i].Id)
  679. if err != nil { return err }
  680. }
  681. return nil
  682. }
  683. func insertUser(db *sql.DB, user User) (User, error){
  684. var query string
  685. var row *sql.Row
  686. var err error
  687. var id int // Inserted user's id
  688. query = `INSERT INTO user
  689. (
  690. email,
  691. first_name,
  692. last_name,
  693. password,
  694. created,
  695. role,
  696. verified,
  697. last_login
  698. )
  699. VALUES (?, ?, ?, sha2(?, 256), NOW(), ?, ?, NOW())
  700. RETURNING id
  701. `
  702. row = db.QueryRow(query,
  703. user.Email,
  704. user.FirstName,
  705. user.LastName,
  706. user.Password,
  707. user.Role,
  708. user.Verified,
  709. )
  710. err = row.Scan(&id)
  711. if err != nil { return User{}, err }
  712. users, err := queryUsers(db, id)
  713. if err != nil { return User{}, err }
  714. return users[0], nil
  715. }
  716. func updateUser(user User, db *sql.DB) error {
  717. query := `
  718. UPDATE user
  719. SET
  720. email = CASE @e := ? WHEN '' THEN email ELSE @e END,
  721. first_name = CASE @fn := ? WHEN '' THEN first_name ELSE @fn END,
  722. last_name = CASE @ln := ? WHEN '' THEN last_name ELSE @ln END,
  723. role = CASE @r := ? WHEN '' THEN role ELSE @r END,
  724. password = CASE @p := ? WHEN '' THEN password ELSE sha2(@p, 256) END
  725. WHERE id = ?
  726. `
  727. _, err := db.Exec(query,
  728. user.Email,
  729. user.FirstName,
  730. user.LastName,
  731. user.Role,
  732. user.Password,
  733. user.Id,
  734. )
  735. return err
  736. }
  737. func getUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  738. claims, err := getClaims(r)
  739. if err != nil { w.WriteHeader(500); return }
  740. users, err := queryUsers(db, claims.Id)
  741. if err != nil { w.WriteHeader(422); log.Println(err); return }
  742. json.NewEncoder(w).Encode(users)
  743. }
  744. func getUsers(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  745. users, err := queryUsers(db, 0)
  746. if err != nil {
  747. w.WriteHeader(http.StatusInternalServerError)
  748. return
  749. }
  750. json.NewEncoder(w).Encode(users)
  751. }
  752. // Updates a user using only specified values in the JSON body
  753. func patchUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  754. var user User
  755. err := json.NewDecoder(r.Body).Decode(&user)
  756. _, err = mail.ParseAddress(user.Email)
  757. if err != nil { http.Error(w, "Invalid email.", 422); return }
  758. if roles[user.Role] == 0 {
  759. http.Error(w, "Invalid role.", 422)
  760. return
  761. }
  762. err = updateUser(user, db)
  763. if err != nil { http.Error(w, "Bad form values.", 422); return }
  764. users, err := queryUsers(db, user.Id)
  765. if err != nil { http.Error(w, "Bad form values.", 422); return }
  766. json.NewEncoder(w).Encode(users[0])
  767. }
  768. // Update specified fields of the user specified in the claim
  769. func patchSelf(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  770. claim, err := getClaims(r)
  771. var user User
  772. json.NewDecoder(r.Body).Decode(&user)
  773. // First check that the target user to be updated is the same as the claim id, and
  774. // their role is unchanged.
  775. if err != nil || claim.Id != user.Id {
  776. http.Error(w, "Target user's id does not match claim.", 401)
  777. return
  778. }
  779. if claim.Role != user.Role && user.Role != "" {
  780. http.Error(w, "Administrator required to escalate role.", 401)
  781. return
  782. }
  783. patchUser(w, db, r)
  784. }
  785. func deleteUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  786. var user User
  787. err := json.NewDecoder(r.Body).Decode(&user)
  788. if err != nil {
  789. http.Error(w, "Invalid fields.", 422)
  790. return
  791. }
  792. query := `DELETE FROM user WHERE id = ?`
  793. _, err = db.Exec(query, user.Id)
  794. if err != nil {
  795. http.Error(w, "Could not delete.", 500)
  796. }
  797. }
  798. func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  799. var user User
  800. err := json.NewDecoder(r.Body).Decode(&user)
  801. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  802. _, err = mail.ParseAddress(user.Email)
  803. if err != nil { http.Error(w, "Invalid email.", 422); return }
  804. if roles[user.Role] == 0 {
  805. http.Error(w, "Invalid role.", 422)
  806. }
  807. user, err = insertUser(db, user)
  808. if err != nil { http.Error(w, "Error creating user.", 422); return }
  809. json.NewEncoder(w).Encode(user)
  810. }
  811. func fetchAvatar(db *sql.DB, user int) ( []byte, error ) {
  812. var img []byte
  813. var query string
  814. var err error
  815. query = `SELECT
  816. avatar
  817. FROM user WHERE user.id = ?
  818. `
  819. row := db.QueryRow(query, user)
  820. err = row.Scan(&img)
  821. if err != nil {
  822. return img, err
  823. }
  824. return img, nil
  825. }
  826. func insertAvatar(db *sql.DB, user int, img []byte) error {
  827. query := `UPDATE user
  828. SET avatar = ?
  829. WHERE id = ?
  830. `
  831. _, err := db.Exec(query, img, user)
  832. if err != nil {
  833. return err
  834. }
  835. return nil
  836. }
  837. func fetchLetterhead(db *sql.DB, user int) ( []byte, error ) {
  838. var img []byte
  839. var query string
  840. var err error
  841. query = `SELECT
  842. letterhead
  843. FROM user WHERE user.id = ?
  844. `
  845. row := db.QueryRow(query, user)
  846. err = row.Scan(&img)
  847. if err != nil {
  848. return img, err
  849. }
  850. return img, nil
  851. }
  852. func insertLetterhead(db *sql.DB, user int, img []byte) error {
  853. query := `UPDATE user
  854. SET letterhead = ?
  855. WHERE id = ?
  856. `
  857. _, err := db.Exec(query, img, user)
  858. if err != nil {
  859. return err
  860. }
  861. return nil
  862. }
  863. func setAvatar(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  864. var validTypes []string = []string{"image/png", "image/jpeg"}
  865. var isValidType bool
  866. claims, err := getClaims(r)
  867. if err != nil { http.Error(w, "Invalid token.", 422); return }
  868. img, err := io.ReadAll(r.Body)
  869. if err != nil { http.Error(w, "Invalid file.", 422); return }
  870. for _, v := range validTypes {
  871. if v == http.DetectContentType(img) { isValidType = true }
  872. }
  873. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  874. err = insertAvatar(db, claims.Id, img)
  875. if err != nil { http.Error(w, "Could not insert.", 500); return }
  876. }
  877. func getAvatar(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  878. claims, err := getClaims(r)
  879. if err != nil { http.Error(w, "Invalid token.", 422); return }
  880. img, err := fetchAvatar(db, claims.Id)
  881. if err != nil { http.Error(w, "Could not retrieve.", 500); return }
  882. w.Header().Set("Content-Type", http.DetectContentType(img))
  883. w.Write(img)
  884. }
  885. func setLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  886. var validTypes []string = []string{"image/png", "image/jpeg"}
  887. var isValidType bool
  888. claims, err := getClaims(r)
  889. if err != nil { http.Error(w, "Invalid token.", 422); return }
  890. img, err := io.ReadAll(r.Body)
  891. if err != nil { http.Error(w, "Invalid file.", 422); return }
  892. for _, v := range validTypes {
  893. if v == http.DetectContentType(img) { isValidType = true }
  894. }
  895. if !isValidType { http.Error(w, "Invalid file type.", 422); return }
  896. err = insertLetterhead(db, claims.Id, img)
  897. if err != nil { http.Error(w, "Could not insert.", 500); return }
  898. }
  899. func getLetterhead(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  900. claims, err := getClaims(r)
  901. if err != nil { http.Error(w, "Invalid token.", 422); return }
  902. img, err := fetchLetterhead(db, claims.Id)
  903. if err != nil { http.Error(w, "Could not retrieve.", 500); return }
  904. w.Header().Set("Content-Type", http.DetectContentType(img))
  905. w.Write(img)
  906. }
  907. func queryBorrower(db *sql.DB, id int) ( Borrower, error ) {
  908. var borrower Borrower
  909. var query string
  910. var err error
  911. query = `SELECT
  912. l.id,
  913. l.credit_score,
  914. l.num,
  915. l.monthly_income
  916. FROM borrower l WHERE l.id = ?
  917. `
  918. row := db.QueryRow(query, id)
  919. err = row.Scan(
  920. borrower.Id,
  921. borrower.Credit,
  922. borrower.Num,
  923. borrower.Income,
  924. )
  925. if err != nil {
  926. return borrower, err
  927. }
  928. return borrower, nil
  929. }
  930. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  931. func getResults(db *sql.DB, e int, id int) ( []Result, error ) {
  932. var results []Result
  933. var query string
  934. var rows *sql.Rows
  935. var err error
  936. query = `SELECT
  937. r.id,
  938. loan_id,
  939. loan_payment,
  940. total_monthly,
  941. total_fees,
  942. total_credits,
  943. cash_to_close
  944. FROM estimate_result r
  945. INNER JOIN loan
  946. ON r.loan_id = loan.id
  947. WHERE r.id = CASE @e := ? WHEN 0 THEN r.id ELSE @e END
  948. AND loan.estimate_id = ?
  949. `
  950. rows, err = db.Query(query, id, e)
  951. if err != nil {
  952. return results, err
  953. }
  954. defer rows.Close()
  955. for rows.Next() {
  956. var result Result
  957. if err := rows.Scan(
  958. &result.Id,
  959. &result.LoanId,
  960. &result.LoanPayment,
  961. &result.TotalMonthly,
  962. &result.TotalFees,
  963. &result.TotalCredits,
  964. &result.CashToClose,
  965. )
  966. err != nil {
  967. return results, err
  968. }
  969. results = append(results, result)
  970. }
  971. // Prevents runtime panics
  972. // if len(results) == 0 { return results, errors.New("Result not found.") }
  973. return results, nil
  974. }
  975. // Must have an estimate ID 'e', but not necessarily a loan id 'id'
  976. func getLoans(db *sql.DB, e int, id int) ( []Loan, error ) {
  977. var loans []Loan
  978. var query string
  979. var rows *sql.Rows
  980. var err error
  981. query = `SELECT
  982. l.id,
  983. l.type_id,
  984. l.estimate_id,
  985. l.amount,
  986. l.term,
  987. l.interest,
  988. l.ltv,
  989. l.dti,
  990. l.hoi,
  991. l.tax,
  992. l.name
  993. FROM loan l WHERE l.id = CASE @e := ? WHEN 0 THEN l.id ELSE @e END AND
  994. l.estimate_id = ?
  995. `
  996. rows, err = db.Query(query, id, e)
  997. if err != nil {
  998. return loans, err
  999. }
  1000. defer rows.Close()
  1001. for rows.Next() {
  1002. var loan Loan
  1003. if err := rows.Scan(
  1004. &loan.Id,
  1005. &loan.Type.Id,
  1006. &loan.EstimateId,
  1007. &loan.Amount,
  1008. &loan.Term,
  1009. &loan.Interest,
  1010. &loan.Ltv,
  1011. &loan.Dti,
  1012. &loan.Hoi,
  1013. &loan.Tax,
  1014. &loan.Name,
  1015. )
  1016. err != nil {
  1017. return loans, err
  1018. }
  1019. mi, err := getMi(db, loan.Id)
  1020. if err != nil {
  1021. return loans, err
  1022. }
  1023. loan.Mi = mi
  1024. fees, err := getFees(db, loan.Id)
  1025. if err != nil {
  1026. return loans, err
  1027. }
  1028. loan.Fees = fees
  1029. loan.Type, err = getLoanType(db, loan.Type.Id)
  1030. if err != nil {
  1031. return loans, err
  1032. }
  1033. loans = append(loans, loan)
  1034. }
  1035. // Prevents runtime panics
  1036. if len(loans) == 0 { return loans, errors.New("Loan not found.") }
  1037. return loans, nil
  1038. }
  1039. func getEstimates(db *sql.DB, id int, user int) ( []Estimate, error ) {
  1040. var estimates []Estimate
  1041. var query string
  1042. var rows *sql.Rows
  1043. var err error
  1044. query = `SELECT
  1045. id,
  1046. user_id,
  1047. borrower_id,
  1048. transaction,
  1049. price,
  1050. property,
  1051. occupancy,
  1052. zip,
  1053. pud
  1054. FROM estimate WHERE id = CASE @e := ? WHEN 0 THEN id ELSE @e END AND
  1055. user_id = CASE @e := ? WHEN 0 THEN user_id ELSE @e END
  1056. `
  1057. rows, err = db.Query(query, id, user)
  1058. if err != nil {
  1059. return estimates, err
  1060. }
  1061. defer rows.Close()
  1062. for rows.Next() {
  1063. var estimate Estimate
  1064. if err := rows.Scan(
  1065. &estimate.Id,
  1066. &estimate.User,
  1067. &estimate.Borrower.Id,
  1068. &estimate.Transaction,
  1069. &estimate.Price,
  1070. &estimate.Property,
  1071. &estimate.Occupancy,
  1072. &estimate.Zip,
  1073. &estimate.Pud,
  1074. )
  1075. err != nil {
  1076. return estimates, err
  1077. }
  1078. borrower, err := getBorrower(db, estimate.Borrower.Id)
  1079. if err != nil {
  1080. return estimates, err
  1081. }
  1082. estimate.Borrower = borrower
  1083. estimate.Results, err = getResults(db, estimate.Id, 0)
  1084. if err != nil {
  1085. return estimates, err
  1086. }
  1087. estimates = append(estimates, estimate)
  1088. }
  1089. // Prevents runtime panics
  1090. if len(estimates) == 0 { return estimates, errors.New("Estimate not found.") }
  1091. for i := range estimates {
  1092. estimates[i].Loans, err = getLoans(db, estimates[i].Id, 0)
  1093. if err != nil { return estimates, err }
  1094. }
  1095. return estimates, nil
  1096. }
  1097. // Accepts a borrower struct and returns the id of the inserted borrower and
  1098. // any related error.
  1099. func insertBorrower(db *sql.DB, borrower Borrower) (int, error) {
  1100. var query string
  1101. var row *sql.Row
  1102. var err error
  1103. var id int // Inserted loan's id
  1104. query = `INSERT INTO borrower
  1105. (
  1106. credit_score,
  1107. monthly_income,
  1108. num
  1109. )
  1110. VALUES (?, ?, ?)
  1111. RETURNING id
  1112. `
  1113. row = db.QueryRow(query,
  1114. borrower.Credit,
  1115. borrower.Income,
  1116. borrower.Num,
  1117. )
  1118. err = row.Scan(&id)
  1119. if err != nil { return 0, err }
  1120. return id, nil
  1121. }
  1122. func insertMi(db *sql.DB, mi MI) (int, error) {
  1123. var id int
  1124. query := `INSERT INTO mi
  1125. (
  1126. type,
  1127. label,
  1128. lender,
  1129. rate,
  1130. premium,
  1131. upfront,
  1132. five_year_total,
  1133. initial_premium,
  1134. initial_rate,
  1135. initial_amount
  1136. )
  1137. VALUES (?, ?, ?, ?, ?, ?, ?)
  1138. RETURNING id`
  1139. row := db.QueryRow(query,
  1140. &mi.Type,
  1141. &mi.Label,
  1142. &mi.Lender,
  1143. &mi.Rate,
  1144. &mi.Premium,
  1145. &mi.Upfront,
  1146. &mi.FiveYearTotal,
  1147. &mi.InitialAllInPremium,
  1148. &mi.InitialAllInRate,
  1149. &mi.InitialAmount,
  1150. )
  1151. err := row.Scan(&id)
  1152. if err != nil { return 0, err }
  1153. return id, nil
  1154. }
  1155. func insertFee(db *sql.DB, fee Fee) (int, error) {
  1156. var id int
  1157. query := `INSERT INTO fee
  1158. (loan_id, amount, perc, type, notes, name, category)
  1159. VALUES (?, ?, ?, ?, ?, ?, ?)
  1160. RETURNING id`
  1161. row := db.QueryRow(query,
  1162. fee.LoanId,
  1163. fee.Amount,
  1164. fee.Perc,
  1165. fee.Type,
  1166. fee.Notes,
  1167. fee.Name,
  1168. fee.Category,
  1169. )
  1170. err := row.Scan(&id)
  1171. if err != nil { return 0, err }
  1172. return id, nil
  1173. }
  1174. func insertLoan(db *sql.DB, loan Loan) (Loan, error){
  1175. var query string
  1176. var row *sql.Row
  1177. var err error
  1178. var id int // Inserted loan's id
  1179. query = `INSERT INTO loan
  1180. (
  1181. estimate_id,
  1182. type_id,
  1183. amount,
  1184. term,
  1185. interest,
  1186. ltv,
  1187. dti,
  1188. hoi,
  1189. tax,
  1190. name
  1191. )
  1192. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1193. RETURNING id
  1194. `
  1195. row = db.QueryRow(query,
  1196. loan.EstimateId,
  1197. loan.Type.Id,
  1198. loan.Amount,
  1199. loan.Term,
  1200. loan.Interest,
  1201. loan.Ltv,
  1202. loan.Dti,
  1203. loan.Hoi,
  1204. loan.Tax,
  1205. loan.Name,
  1206. )
  1207. err = row.Scan(&id)
  1208. if err != nil { return loan, err }
  1209. _, err = insertMi(db, loan.Mi)
  1210. if err != nil { return loan, err }
  1211. for i := range loan.Fees {
  1212. _, err := insertFee(db, loan.Fees[i])
  1213. if err != nil { return loan, err }
  1214. }
  1215. loans, err := getLoans(db, id, 0)
  1216. if err != nil { return Loan{}, err }
  1217. return loans[0], nil
  1218. }
  1219. func insertEstimate(db *sql.DB, estimate Estimate) (Estimate, error){
  1220. var query string
  1221. var row *sql.Row
  1222. var err error
  1223. // var id int // Inserted estimate's id
  1224. estimate.Borrower.Id, err = insertBorrower(db, estimate.Borrower)
  1225. if err != nil { return Estimate{}, err }
  1226. query = `INSERT INTO estimate
  1227. (
  1228. user_id,
  1229. borrower_id,
  1230. transaction,
  1231. price,
  1232. property,
  1233. occupancy,
  1234. zip,
  1235. pud
  1236. )
  1237. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  1238. RETURNING id
  1239. `
  1240. row = db.QueryRow(query,
  1241. estimate.User,
  1242. estimate.Borrower.Id,
  1243. estimate.Transaction,
  1244. estimate.Price,
  1245. estimate.Property,
  1246. estimate.Occupancy,
  1247. estimate.Zip,
  1248. estimate.Pud,
  1249. )
  1250. err = row.Scan(&estimate.Id)
  1251. if err != nil { return Estimate{}, err }
  1252. for _, l := range estimate.Loans {
  1253. l.EstimateId = estimate.Id
  1254. _, err = insertLoan(db, l)
  1255. if err != nil { return estimate, err }
  1256. }
  1257. estimates, err := getEstimates(db, estimate.Id, 0)
  1258. if err != nil { return Estimate{}, err }
  1259. return estimates[0], nil
  1260. }
  1261. func createEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1262. var estimate Estimate
  1263. err := json.NewDecoder(r.Body).Decode(&estimate)
  1264. if err != nil { http.Error(w, "Invalid fields.", 422); return }
  1265. claims, err := getClaims(r)
  1266. estimate.User = claims.Id
  1267. estimate, err = insertEstimate(db, estimate)
  1268. if err != nil { http.Error(w, err.Error(), 422); return }
  1269. estimate.Results = makeResults(estimate)
  1270. err = insertResults(db, estimate.Results)
  1271. if err != nil { http.Error(w, err.Error(), 500); return }
  1272. e, err := getEstimates(db, estimate.Id, 0)
  1273. if err != nil { http.Error(w, err.Error(), 500); return }
  1274. json.NewEncoder(w).Encode(e[0])
  1275. }
  1276. // Query all estimates that belong to the current user
  1277. func fetchEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1278. var estimates []Estimate
  1279. claims, err := getClaims(r)
  1280. estimates, err = getEstimates(db, 0, claims.Id)
  1281. if err != nil { http.Error(w, err.Error(), 500); return }
  1282. json.NewEncoder(w).Encode(estimates)
  1283. }
  1284. func checkConventional(l Loan, b Borrower) error {
  1285. if b.Credit < 620 {
  1286. return errors.New("Credit score too low for conventional loan")
  1287. }
  1288. // Buyer needs to put down 20% to avoid mortgage insurance
  1289. if (l.Ltv > 80 && l.Mi.Rate == 0) {
  1290. return errors.New(fmt.Sprintln(
  1291. l.Name,
  1292. "down payment must be 20% to avoid insurance",
  1293. ))
  1294. }
  1295. return nil
  1296. }
  1297. func checkFHA(l Loan, b Borrower) error {
  1298. if b.Credit < 500 {
  1299. return errors.New("Credit score too low for FHA loan")
  1300. }
  1301. if (l.Ltv > 96.5) {
  1302. return errors.New("FHA down payment must be at least 3.5%")
  1303. }
  1304. if (b.Credit < 580 && l.Ltv > 90) {
  1305. return errors.New("FHA down payment must be at least 10%")
  1306. }
  1307. // Debt-to-income must be below 45% if credit score is below 580.
  1308. if (b.Credit < 580 && l.Dti > 45) {
  1309. return errors.New(fmt.Sprintln(
  1310. l.Name, "debt to income is too high for credit score.",
  1311. ))
  1312. }
  1313. return nil
  1314. }
  1315. // Loan option for veterans with no set rules
  1316. func checkVA(l Loan, b Borrower) error {
  1317. return nil
  1318. }
  1319. // Loan option for residents of rural areas with no set rules
  1320. func checkUSDA(l Loan, b Borrower) error {
  1321. return nil
  1322. }
  1323. // Should also check loan amount limit maybe with an API.
  1324. func checkEstimate(e Estimate) error {
  1325. if e.Property == "" { return errors.New("Empty property type") }
  1326. if e.Price == 0 { return errors.New("Empty property price") }
  1327. if e.Borrower.Num == 0 {
  1328. return errors.New("Missing number of borrowers")
  1329. }
  1330. if e.Borrower.Credit == 0 {
  1331. return errors.New("Missing borrower credit score")
  1332. }
  1333. if e.Borrower.Income == 0 {
  1334. return errors.New("Missing borrower credit income")
  1335. }
  1336. for _, l := range e.Loans {
  1337. if l.Amount == 0 {
  1338. return errors.New(fmt.Sprintln(l.Name, "loan amount cannot be zero"))
  1339. }
  1340. if l.Term == 0 {
  1341. return errors.New(fmt.Sprintln(l.Name, "loan term cannot be zero"))
  1342. }
  1343. if l.Interest == 0 {
  1344. return errors.New(fmt.Sprintln(l.Name, "loan interest cannot be zero"))
  1345. }
  1346. // Can be used to check rules for specific loan types
  1347. var err error
  1348. switch l.Type.Id {
  1349. case 1:
  1350. err = checkConventional(l, e.Borrower)
  1351. case 2:
  1352. err = checkFHA(l, e.Borrower)
  1353. case 3:
  1354. err = checkVA(l, e.Borrower)
  1355. case 4:
  1356. err = checkUSDA(l, e.Borrower)
  1357. default:
  1358. err = errors.New("Invalid loan type")
  1359. }
  1360. if err != nil { return err }
  1361. }
  1362. return nil
  1363. }
  1364. func validateEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) {
  1365. var estimate Estimate
  1366. err := json.NewDecoder(r.Body).Decode(&estimate)
  1367. if err != nil { http.Error(w, err.Error(), 422); return }
  1368. err = checkEstimate(estimate)
  1369. if err != nil { http.Error(w, err.Error(), 406); return }
  1370. }
  1371. func showPDF(w http.ResponseWriter, r *http.Request) {
  1372. // var args []string
  1373. // p := r.URL.Path
  1374. db, err := sql.Open("mysql",
  1375. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
  1376. config["DBUser"],
  1377. config["DBPass"]))
  1378. // w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  1379. err = db.Ping()
  1380. if err != nil {
  1381. fmt.Println("Bad database configuration: %v\n", err)
  1382. panic(err)
  1383. // maybe os.Exit(1) instead
  1384. }
  1385. var pa = template.Must(template.ParseFiles("views/master.tpl",
  1386. "views/test.tpl"))
  1387. // claims, err := getClaims(r)
  1388. if err != nil { w.WriteHeader(500); return }
  1389. users, err := queryUsers(db, 1)
  1390. info := struct {
  1391. Title string
  1392. Name string
  1393. User User
  1394. }{
  1395. Title: "test PDF",
  1396. Name: "idk-random-name",
  1397. User: users[0],
  1398. }
  1399. // fmt.Println(info)
  1400. err = pa.Execute(w, info)
  1401. if err != nil {fmt.Println(err)}
  1402. }
  1403. func api(w http.ResponseWriter, r *http.Request) {
  1404. var args []string
  1405. p := r.URL.Path
  1406. db, err := sql.Open("mysql",
  1407. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
  1408. config["DBUser"],
  1409. config["DBPass"]))
  1410. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  1411. err = db.Ping()
  1412. if err != nil {
  1413. fmt.Println("Bad database configuration: %v\n", err)
  1414. panic(err)
  1415. // maybe os.Exit(1) instead
  1416. }
  1417. switch {
  1418. case match(p, "/api/login", &args) &&
  1419. r.Method == http.MethodPost:
  1420. login(w, db, r)
  1421. case match(p, "/api/token", &args) &&
  1422. r.Method == http.MethodGet && guard(r, 1):
  1423. getToken(w, db, r)
  1424. case match(p, "/api/users", &args) && // Array of all users
  1425. r.Method == http.MethodGet && guard(r, 3):
  1426. getUsers(w, db, r)
  1427. case match(p, "/api/user", &args) &&
  1428. r.Method == http.MethodGet && guard(r, 1):
  1429. getUser(w, db, r)
  1430. case match(p, "/api/user", &args) &&
  1431. r.Method == http.MethodPost &&
  1432. guard(r, 3):
  1433. createUser(w, db, r)
  1434. case match(p, "/api/user", &args) &&
  1435. r.Method == http.MethodPatch &&
  1436. guard(r, 3): // For admin to modify any user
  1437. patchUser(w, db, r)
  1438. case match(p, "/api/user", &args) &&
  1439. r.Method == http.MethodPatch &&
  1440. guard(r, 2): // For employees to modify own accounts
  1441. patchSelf(w, db, r)
  1442. case match(p, "/api/user", &args) &&
  1443. r.Method == http.MethodDelete &&
  1444. guard(r, 3):
  1445. deleteUser(w, db, r)
  1446. case match(p, "/api/user/avatar", &args) &&
  1447. r.Method == http.MethodGet &&
  1448. guard(r, 1):
  1449. getAvatar(w, db, r)
  1450. case match(p, "/api/user/avatar", &args) &&
  1451. r.Method == http.MethodPost &&
  1452. guard(r, 1):
  1453. setAvatar(w, db, r)
  1454. case match(p, "/api/user/letterhead", &args) &&
  1455. r.Method == http.MethodGet &&
  1456. guard(r, 1):
  1457. getLetterhead(w, db, r)
  1458. case match(p, "/api/user/letterhead", &args) &&
  1459. r.Method == http.MethodPost &&
  1460. guard(r, 1):
  1461. setLetterhead(w, db, r)
  1462. case match(p, "/api/fees", &args) &&
  1463. r.Method == http.MethodGet &&
  1464. guard(r, 1):
  1465. getFeesTemp(w, db, r)
  1466. case match(p, "/api/fee", &args) &&
  1467. r.Method == http.MethodPost &&
  1468. guard(r, 1):
  1469. createFeesTemp(w, db, r)
  1470. case match(p, "/api/fee", &args) &&
  1471. r.Method == http.MethodDelete &&
  1472. guard(r, 1):
  1473. deleteFeeTemp(w, db, r)
  1474. case match(p, "/api/estimates", &args) &&
  1475. r.Method == http.MethodGet &&
  1476. guard(r, 1):
  1477. fetchEstimate(w, db, r)
  1478. case match(p, "/api/estimate", &args) &&
  1479. r.Method == http.MethodPost &&
  1480. guard(r, 1):
  1481. createEstimate(w, db, r)
  1482. case match(p, "/api/estimate/validate", &args) &&
  1483. r.Method == http.MethodPost &&
  1484. guard(r, 1):
  1485. validateEstimate(w, db, r)
  1486. case match(p, "/api/estimate/summarize", &args) &&
  1487. r.Method == http.MethodPost &&
  1488. guard(r, 1):
  1489. summarize(w, db, r)
  1490. default:
  1491. http.Error(w, "Invalid route or token", 404)
  1492. }
  1493. db.Close()
  1494. }
  1495. func route(w http.ResponseWriter, r *http.Request) {
  1496. var page Page
  1497. var args []string
  1498. p := r.URL.Path
  1499. switch {
  1500. case r.Method == "GET" && match(p, "/", &args):
  1501. page = pages[ "home" ]
  1502. case match(p, "/terms", &args):
  1503. page = pages[ "terms" ]
  1504. case match(p, "/app", &args):
  1505. page = pages[ "app" ]
  1506. case match(p, "/test", &args):
  1507. showPDF(w, r)
  1508. return
  1509. default:
  1510. http.NotFound(w, r)
  1511. return
  1512. }
  1513. page.Render(w)
  1514. }
  1515. func main() {
  1516. files := http.FileServer(http.Dir(""))
  1517. http.Handle("/assets/", files)
  1518. http.HandleFunc("/api/", api)
  1519. http.HandleFunc("/", route)
  1520. log.Fatal(http.ListenAndServe(address, nil))
  1521. }