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.
 
 
 
 
 
 

508 lines
10 KiB

  1. package main
  2. import (
  3. "net/http"
  4. "log"
  5. "sync"
  6. "regexp"
  7. "html/template"
  8. "database/sql"
  9. _ "github.com/go-sql-driver/mysql"
  10. "fmt"
  11. "encoding/json"
  12. // "io"
  13. "strconv"
  14. "bytes"
  15. )
  16. type Page struct {
  17. tpl *template.Template
  18. Title string
  19. Name string
  20. }
  21. type Borrower struct {
  22. Id int `json:"id"`
  23. Credit int `json:"credit"`
  24. Income int `json:"income"`
  25. Num int `json:"num"`
  26. }
  27. type FeeTemplate struct {
  28. Id int `json:"id"`
  29. User int `json:"user"`
  30. Branch int `json:"branch"`
  31. Amount int `json:"amount"`
  32. Perc int `json:"perc"`
  33. Type string `json:"type"`
  34. Notes string `json:"notes"`
  35. Name string `json:"name"`
  36. Category string `json:"category"`
  37. Auto bool `json:"auto"`
  38. }
  39. type Fee struct {
  40. Id int `json:"id"`
  41. LoanId int `json:"loan_id"`
  42. Amount int `json:"amount"`
  43. Perc int `json:"perc"`
  44. Type string `json:"type"`
  45. Notes string `json:"notes"`
  46. Name string `json:"name"`
  47. Category string `json:"category"`
  48. }
  49. type LoanType struct {
  50. Id int `json:"id"`
  51. User int `json:"user"`
  52. Branch int `json:"branch"`
  53. Name string `json:"name"`
  54. }
  55. type Loan struct {
  56. Id int `json:id`
  57. EstimateId int `json:estimate_id`
  58. Type LoanType `json:"loanType"`
  59. Amount int `json:"loanAmount"`
  60. Term int `json:"term"`
  61. Ltv int `json:"ltv"`
  62. Dti int `json:"dti"`
  63. Hoi int `json:"hoi"`
  64. Interest int `json:"interest"`
  65. Lender string `json:"lender"`
  66. MiName string `json:"miName"`
  67. MiAmount int `json:"miAmount"`
  68. Fees []Fee `json:"fees"`
  69. Name string `json:"name"`
  70. }
  71. type Estimate struct {
  72. Id int `json:"id"`
  73. User int `json:"user"`
  74. Borrower Borrower `json:"borrower"`
  75. Transaction string `json:"transaction"`
  76. Price int `json:"price"`
  77. Property string `json:"property"`
  78. Occupancy string `json:"occupancy"`
  79. Zip string `json:"zip"`
  80. Pud bool `json:"pud"`
  81. Loans []Loan `json:"loans"`
  82. }
  83. var (
  84. regexen = make(map[string]*regexp.Regexp)
  85. relock sync.Mutex
  86. address = "127.0.0.1:8001"
  87. )
  88. var paths = map[string]string {
  89. "home": "home.tpl",
  90. "terms": "terms.tpl",
  91. "app": "app.tpl",
  92. }
  93. var pages = map[string]Page {
  94. "home": cache("home", "Home"),
  95. "terms": cache("terms", "Terms and Conditions"),
  96. "app": cache("app", "App"),
  97. }
  98. func cache(name string, title string) Page {
  99. var p = []string{"master.tpl", paths[name]}
  100. tpl := template.Must(template.ParseFiles(p...))
  101. return Page{tpl: tpl,
  102. Title: title,
  103. Name: name,
  104. }
  105. }
  106. func (page Page) Render(w http.ResponseWriter) {
  107. err := page.tpl.Execute(w, page)
  108. if err != nil {
  109. log.Print(err)
  110. }
  111. }
  112. func match(path, pattern string, args *[]string) bool {
  113. relock.Lock()
  114. defer relock.Unlock()
  115. regex := regexen[pattern]
  116. if regex == nil {
  117. regex = regexp.MustCompile("^" + pattern + "$")
  118. regexen[pattern] = regex
  119. }
  120. matches := regex.FindStringSubmatch(path)
  121. if len(matches) <= 0 {
  122. return false
  123. }
  124. *args = matches[1:]
  125. return true
  126. }
  127. func getLoanType(
  128. db *sql.DB,
  129. user int,
  130. branch int,
  131. isUser bool) ([]LoanType, error) {
  132. var loans []LoanType
  133. // Should be changed to specify user
  134. rows, err :=
  135. db.Query(`SELECT * FROM loan_type WHERE user_id = ? AND branch_id = ? ` +
  136. "OR (user_id = 0 AND branch_id = 0)", user, branch)
  137. if err != nil {
  138. return nil, fmt.Errorf("loan_type error: %v", err)
  139. }
  140. defer rows.Close()
  141. for rows.Next() {
  142. var loan LoanType
  143. if err := rows.Scan(
  144. &loan.Id,
  145. &loan.User,
  146. &loan.Branch,
  147. &loan.Name)
  148. err != nil {
  149. log.Printf("Error occured fetching loan: %v", err)
  150. return nil, fmt.Errorf("Error occured fetching loan: %v", err)
  151. }
  152. loans = append(loans, loan)
  153. }
  154. log.Printf("The loans: %v", loans)
  155. return loans, nil
  156. }
  157. func getEstimate(db *sql.DB, id int) (Estimate, error) {
  158. var estimate Estimate
  159. // Inner join should always be valid because a borrower is a required
  160. // foreign key.
  161. row := db.QueryRow(
  162. "SELECT * FROM estimate "+
  163. "WHERE id = ? " +
  164. "INNER JOIN borrower ON estimate.borrower = borrower.id",
  165. id)
  166. if err := row.Scan(
  167. &estimate.Id,
  168. &estimate.User,
  169. &estimate.Borrower.Id,
  170. &estimate.Transaction,
  171. &estimate.Price,
  172. &estimate.Property,
  173. &estimate.Occupancy,
  174. &estimate.Zip,
  175. &estimate.Pud,
  176. )
  177. err != nil {
  178. return estimate, fmt.Errorf("Estimate scanning error: %v", err)
  179. }
  180. return estimate, nil
  181. }
  182. func getFees(db *sql.DB, loan int) ([]Fee, error) {
  183. var fees []Fee
  184. rows, err := db.Query(
  185. "SELECT * FROM fees " +
  186. "WHERE loan_id = ?",
  187. loan)
  188. if err != nil {
  189. return nil, fmt.Errorf("Fee query error %v", err)
  190. }
  191. defer rows.Close()
  192. for rows.Next() {
  193. var fee Fee
  194. if err := rows.Scan(
  195. &fee.Id,
  196. &fee.LoanId,
  197. &fee.Amount,
  198. &fee.Perc,
  199. &fee.Type,
  200. &fee.Notes,
  201. &fee.Name,
  202. &fee.Category,
  203. )
  204. err != nil {
  205. return nil, fmt.Errorf("Fees scanning error: %v", err)
  206. }
  207. fees = append(fees, fee)
  208. }
  209. return fees, nil
  210. }
  211. // Fetch fees from the database
  212. func getFeesTemp(db *sql.DB, user int) ([]FeeTemplate, error) {
  213. var fees []FeeTemplate
  214. rows, err := db.Query(
  215. "SELECT * FROM fee_template " +
  216. "WHERE user_id = ? OR user_id = 0",
  217. user)
  218. if err != nil {
  219. return nil, fmt.Errorf("Fee template query error %v", err)
  220. }
  221. defer rows.Close()
  222. for rows.Next() {
  223. var fee FeeTemplate
  224. if err := rows.Scan(
  225. &fee.Id,
  226. &fee.User,
  227. &fee.Branch,
  228. &fee.Amount,
  229. &fee.Perc,
  230. &fee.Type,
  231. &fee.Notes,
  232. &fee.Name,
  233. &fee.Category,
  234. &fee.Auto)
  235. err != nil {
  236. return nil, fmt.Errorf("FeesTemplate scanning error: %v", err)
  237. }
  238. fees = append(fees, fee)
  239. }
  240. est, err := getEstimate(db, 1)
  241. fmt.Printf("the estimate: %v,\nthe error %v\n", est, err)
  242. // getMi(db, getEstimate(db, 1), getBorrower(db, 1))
  243. return fees, nil
  244. }
  245. func getLoans(db *sql.DB, estimate int) ([]Loan, error) {
  246. var loans []Loan
  247. query := `SELECT
  248. loan.id, loan.amount, loan.term, loan.interest, loan.ltv, loan.dti,
  249. loan.hoi, loan.mi_name, loan.mi_amount
  250. loan_type.id, loan_type.user_id, loan_type.branch_id, loan_type.name
  251. FROM loan INNER JOIN loan_type ON loan.type_id = loan_type(id)
  252. WHERE loan.estimate_id = ?
  253. `
  254. rows, err := db.Query(query, estimate)
  255. if err != nil {
  256. return nil, fmt.Errorf("Loan query error %v", err)
  257. }
  258. defer rows.Close()
  259. for rows.Next() {
  260. var loan Loan
  261. if err := rows.Scan(
  262. &loan.Id,
  263. &loan.Amount,
  264. &loan.Term,
  265. &loan.Interest,
  266. &loan.Ltv,
  267. &loan.Dti,
  268. &loan.Hoi,
  269. &loan.MiName,
  270. &loan.MiAmount,
  271. &loan.Type.Id,
  272. &loan.Type.User,
  273. &loan.Type.Branch,
  274. &loan.Type.Name,
  275. )
  276. err != nil {
  277. return loans, fmt.Errorf("Loans scanning error: %v", err)
  278. }
  279. loans = append(loans, loan)
  280. }
  281. est, err := getEstimate(db, 1)
  282. fmt.Printf("the loan: %v,\nthe error %v\n", est, err)
  283. // getMi(db, getEstimate(db, 1), getBorrower(db, 1))
  284. return loans, nil
  285. }
  286. func getBorrower(db *sql.DB, id int) (Borrower, error) {
  287. var borrower Borrower
  288. row := db.QueryRow(
  289. "SELECT * FROM borrower " +
  290. "WHERE id = ? LIMIT 1",
  291. id)
  292. if err := row.Scan(
  293. &borrower.Id,
  294. &borrower.Credit,
  295. &borrower.Income,
  296. &borrower.Num,
  297. )
  298. err != nil {
  299. return borrower, fmt.Errorf("Borrower scanning error: %v", err)
  300. }
  301. return borrower, nil
  302. }
  303. func getMi(db *sql.DB, estimate Estimate, borrower Borrower) {
  304. // body := map[string]string{
  305. // "zipCode": estimate.Zip,
  306. // // "stateCode": "CA",
  307. // // "address": "",
  308. // "propertyTypeCode": "SFO",
  309. // "occupancyTypeCode": "PRS",
  310. // "loanPurposeCode": "PUR",
  311. // "loanAmount": "1000000",
  312. // "loanToValue": "LTV95",
  313. // "amortizationTerm": "A30",
  314. // "loanTypeCode": "FXD",
  315. // "duLpDecisionCode": "DAE",
  316. // "loanProgramCodes": [],
  317. // "debtToIncome": "5",
  318. // "wholesaleLoan": 0,
  319. // "coveragePercentageCode": "L30",
  320. // "productCode": "BPM",
  321. // "renewalTypeCode": "CON",
  322. // "numberOfBorrowers": 1,
  323. // "coBorrowerCreditScores": [],
  324. // "borrowerCreditScore": "740",
  325. // "masterPolicy": null,
  326. // "selfEmployedIndicator": false,
  327. // "armType": "",
  328. // "userId": 44504
  329. // }
  330. var propertyCodes = map[string]string {
  331. "Single Family Attached": "SFO",
  332. "Single Family Detached": "SFO",
  333. "Condominium Lo-rise": "CON",
  334. "Condominium Hi-rise": "CON",
  335. }
  336. /* var occupancyCodes = map[string]string {
  337. "Primary Residence": "PRS",
  338. "Second Home": "SCH",
  339. "Condominium Lo-rise": "CON",
  340. "Condominium Hi-rise": "CON",
  341. } */
  342. body, _ := json.Marshal(map[string]any{
  343. "zipCode": estimate.Zip,
  344. // "stateCode": "CA",
  345. // "address": "",
  346. "propertyTypeCode": propertyCodes[estimate.Property],
  347. "occupancyTypeCode": "PRS",
  348. "loanPurposeCode": "PUR",
  349. "loanAmount": strconv.Itoa(500000 / 100),
  350. "loanToValue": "LTV95",
  351. "amortizationTerm": "A30",
  352. "loanTypeCode": "FXD",
  353. "duLpDecisionCode": "DAE",
  354. "debtToIncome": 5,
  355. "wholesaleLoan": 0,
  356. "coveragePercentageCode": "L30",
  357. "productCode": "BPM",
  358. "renewalTypeCode": "CON",
  359. "numberOfBorrowers": 1,
  360. "borrowerCreditScore": strconv.Itoa(borrower.Credit),
  361. "masterPolicy": nil,
  362. "selfEmployedIndicator": false,
  363. "armType": "",
  364. "userId": 44504,
  365. })
  366. log.Println(bytes.NewBuffer(body))
  367. }
  368. func validateEstimate() {
  369. return
  370. }
  371. func route(w http.ResponseWriter, r *http.Request) {
  372. var page Page
  373. var args []string
  374. p := r.URL.Path
  375. switch {
  376. case r.Method == "GET" && match(p, "/", &args):
  377. page = pages[ "home" ]
  378. case match(p, "/terms", &args):
  379. page = pages[ "terms" ]
  380. case match(p, "/app", &args):
  381. page = pages[ "app" ]
  382. case match(p, "/assets", &args):
  383. page = pages[ "app" ]
  384. default:
  385. http.NotFound(w, r)
  386. return
  387. }
  388. page.Render(w)
  389. }
  390. func api(w http.ResponseWriter, r *http.Request) {
  391. var args []string
  392. // var response string
  393. p := r.URL.Path
  394. db, err := sql.Open("mysql",
  395. fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
  396. config["DBUser"],
  397. config["DBPass"]))
  398. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  399. err = db.Ping()
  400. if err != nil {
  401. print("Bad database configuration: %v", err)
  402. panic(err)
  403. // maybe os.Exit(1) instead
  404. }
  405. switch {
  406. case match(p, "/api/loans", &args):
  407. resp, err := getLoanType(db, 0, 0, true)
  408. if resp != nil {
  409. json.NewEncoder(w).Encode(resp)
  410. } else {
  411. json.NewEncoder(w).Encode(err)
  412. }
  413. case match(p, "/api/fees", &args):
  414. resp, err := getFeesTemp(db, 0)
  415. if resp != nil {
  416. json.NewEncoder(w).Encode(resp)
  417. } else {
  418. json.NewEncoder(w).Encode(err)
  419. }
  420. }
  421. }
  422. func main() {
  423. files := http.FileServer(http.Dir(""))
  424. http.Handle("/assets/", files)
  425. http.HandleFunc("/api/", api)
  426. http.HandleFunc("/", route)
  427. log.Fatal(http.ListenAndServe(address, nil))
  428. }