Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

508 wiersze
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. }