index.scm 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. (add-to-load-path (dirname (current-filename)))
  2. (use-modules
  3. (web server)
  4. (web request)
  5. (web response)
  6. (web uri)
  7. (decode)
  8. (oop goops)
  9. (submit)
  10. (sxml simple)
  11. (srfi srfi-9) ;;records
  12. ;; (srfi srfi-19)
  13. )
  14. ;; I might be able to use curl to email
  15. ;; https://stackoverflow.com/questions/14722556/using-curl-to-send-email
  16. ;; https://blog.edmdesigner.com/send-email-from-linux-command-line/
  17. ;; guile also has some curl bindings...
  18. ;; joshua@dobby ~$ curl --url 'smtps://smtp.dismail.de:465' -F subject='test email' --ssl-reqd --mail-from 'jbranso@dismail.de' --mail-rcpt 'jbranso@dismail.de' --user 'jbranso@dimail.de:sticky4RunWhy;' --insecure
  19. ;; curl: (67) Login denied
  20. ;; joshua@dobby ~$
  21. ;; I'll need to do server side validation of email
  22. ;;https://www.regular-expressions.info/email.html
  23. ;;http://synthcode.com/scheme/irregex
  24. ;;before I implement a captcha here are 10 things to check for
  25. ;;
  26. ;; 1) validate everything server side. If input has any HTML, do not accept it.
  27. ;; 2) Check for links. Any input should not have links.
  28. ;; 3) check for the right number of POST and GET fields. If there are extra, it's probably a hacking attempt.
  29. ;; 4) Check the HTTP header
  30. ;; spam bots do not normally set a user agent (HTTP_USER_AGENT) or a referring page (HTTP_REFERER). You should certainly ensure the referrer is the page where your form is located.
  31. ;; 5) Use a honeypot field. Have an input field that should be left blank! Set it to display none. A spammer will try
  32. ;; to fill it out.
  33. ;; 6) 90% of computer users use javascript. Use js to checksum the data. The server can then verify that checksum.
  34. ;; 7) Show a verification page. Bots have a tough time verifying data. Show the user data once more, and have them verify. and re-submit.
  35. ;; 8) Time the user response! Put in the form the time that it was generated. Use the user IP address as the encryption key. If the user took 5-10 minutes to complete, then it is probably a human. Otherwise it is probably a bot.
  36. ;; 9) Log everything. This should help me spot hacking attempts.
  37. ;; 10) You could put a captcha in, if a user fails one of the above. You do not always have to show the captcha!
  38. (define navbar
  39. '(nav (@ (class "navbar navbar-expand-lg navbar-light bg-light"))
  40. (a (@ (class "navbar-brand")) "My IFT")
  41. (button (@ (class "navbar-toggler")
  42. (type "button")
  43. (data-toggle "collapse")
  44. (data-target "#navbarSupportedContent")
  45. (aria-controls "navbarSupportedContent")
  46. (aria-expanded "false")
  47. (aria-label "Toggle navigation"))
  48. (span (@ (class "navbar-toggler-icon"))))
  49. (div (@ (class "collapse navbar-collapse")
  50. (id "navbarSupportedcontent"))
  51. (ul (@ (class ("navbar-nav mr-auto")))
  52. ;; (li (@ (class "nav-item"))
  53. ;; (a (@ (class "nav-link")
  54. ;; (href "About"))
  55. ;; "About"))
  56. ;; (li (@ (class "nav-item active"))
  57. ;; (a (@ (class "nav-link")
  58. ;; (href "apply"))
  59. ;; "Apply"))
  60. ))))
  61. (define (templatize title body)
  62. `(html (head (title ,title)
  63. (head
  64. (link
  65. (@ (rel "stylesheet")
  66. (href "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css")
  67. ;;(href "localhost:8081/css/bootstrap.min.css")
  68. )
  69. ))
  70. (body ,navbar ,@body))))
  71. (define (insert-option-values values)
  72. (if (null? values) '()
  73. (let ([value (car values)])
  74. (cons `(options (@ (value ,value))
  75. ,value)
  76. (insert-option-values (cdr values))))))
  77. (define* (my-select id options
  78. #:key
  79. (required #t))
  80. `(select (@ (id ,id)
  81. (name ,id)
  82. ,(if (eq? required #t)
  83. '(required)
  84. '(not-required))
  85. (class "custom-select"))
  86. (option (@ (value "")
  87. (selected ""))
  88. "Choose...")
  89. ,(let loop ([options options])
  90. (if (null? options)
  91. '()
  92. (cons `(option (@ (value ,(car options)))
  93. ,(car options))
  94. (loop (cdr options)))))))
  95. ;; (define-syntax m-basic-form-group
  96. ;; (syntax-rules ()
  97. ;; [(m-basic-form-group the-label id)
  98. ;; '(div (@ (class "form-row"))
  99. ;; (div (@ (class "col-md-8"))
  100. ;; (label the-label)
  101. ;; (input (@ (class "form-control")))
  102. ;; ))]
  103. ;; [(m-basic-form-group the-label id ...)
  104. ;; (begin
  105. ;; '(div (@ (class "col-md-3"))
  106. ;; (label the-label)
  107. ;; (input (@ (class "form-control"))))
  108. ;; (m-basic-form-group the-label id ...))]))
  109. (define* (my-input input-type label id
  110. #:optional options
  111. #:key
  112. (placeholder "")
  113. (required #t))
  114. (let ([input-type input-type])
  115. (cond [(string= input-type "select")
  116. (my-select id options #:required required)]
  117. [(string= input-type "textarea")
  118. `(textarea (@ (id ,id)
  119. (name ,id)
  120. (type ,input-type)
  121. (class "form-control")
  122. ,(if (eq? required #t)
  123. '(required)
  124. '(not-required))
  125. ;; the "" is necessary to make sxml put a
  126. ;; closing </textarea>
  127. (placeholder ,placeholder)) "")]
  128. [else (let ([input "input"])
  129. (when (string= input-type "textarea")
  130. (set! input "textarea"))
  131. `(input (@ (id ,id)
  132. (name ,id)
  133. (type ,input-type)
  134. (class "form-control")
  135. ,(if (eq? required #t)
  136. '(required)
  137. '(not-required))
  138. (placeholder ,placeholder))))])))
  139. ;; I can make this better! (basic-form-group #:options '("red"
  140. ;;"green")) is obviously a select. I could infer that! Also
  141. ;;(basic-form-group #:input-type "select") would have the default
  142. ;;options "No" and "Yes"
  143. (define* (basic-form-group label id
  144. #:key
  145. (input-type "text")
  146. (placeholder "")
  147. (required #t)
  148. options
  149. (width 8))
  150. `(div (@ (class ,(string-append "form-group col-md-"
  151. (number->string width))))
  152. (label ,label)
  153. ,(my-input input-type label id options #:required required)
  154. ;;(input (@ (class "form-control") (placeholder ,placeholder)))
  155. ))
  156. ;; not working
  157. ;; (define-syntax define-record-type*
  158. ;; (syntax-rules ()
  159. ;; ((define-record-type* type
  160. ;; constructor
  161. ;; constructor?
  162. ;; (fieldname var1) ...)
  163. ;; (define-record-type type
  164. ;; (constructor fieldname ...)
  165. ;; constructor?
  166. ;; (fieldname var1) ...))))
  167. ;; this works!!!!
  168. (define-syntax my-define-record-type
  169. (syntax-rules ()
  170. ((my-define-record-type type
  171. constructor
  172. constructor?
  173. (fieldname var1) ...)
  174. (define-record-type type
  175. (constructor fieldname ...)
  176. constructor?
  177. (fieldname var1) ... ))))
  178. (my-define-record-type <bs-form-group>
  179. make-dog
  180. dog?
  181. (age dog-age))
  182. (define-record-type <bs-form-group>
  183. (make-bs-form-group type placeholder)
  184. bs-form-group?
  185. ;; horizontal or vertical
  186. (type bs-form-group)
  187. (placeholder bs-form-group-placeholder))
  188. (define-syntax bs-horizontal-form-group
  189. (syntax-rules (placeholder)
  190. ((bs-horizontal-form-group ((var1 var2)
  191. (placeholder var3)) ...)
  192. (horizontal-form-group var1 var2 #:placeholder var3))))
  193. (define* (horizontal-form-group label id
  194. #:key
  195. (form-class "row")
  196. placeholder
  197. (input-type "text")
  198. (required #t)
  199. options
  200. )
  201. `(div (@ (class "form-group row"))
  202. (label (@ (class "col-md-2")
  203. (for ,id))
  204. ,label)
  205. (div (@ (class "col-md-6"))
  206. ,(my-input input-type label id options #:required required)
  207. (div (@ (class "valid-feedback"))
  208. "Looks good!"))))
  209. (define* (respond #:optional body #:key
  210. (status 200)
  211. (title "My IFT")
  212. (doctype "<!DOCTYPE html>\n")
  213. (content-type-params '((charset . "utf-8")))
  214. (content-type 'text/html)
  215. (extra-headers '())
  216. (sxml (and body (templatize title body))))
  217. (values (build-response
  218. #:code status
  219. #:headers `((content-type
  220. . (,content-type ,@content-type-params))
  221. ,@extra-headers))
  222. (lambda (port)
  223. (if sxml
  224. (begin
  225. (if doctype (display doctype port))
  226. (sxml->xml sxml port))))))
  227. (define (request-path-components request)
  228. (split-and-decode-uri-path (uri-path (request-uri request))))
  229. ;; Paste this in your REPL
  230. (define (not-found request)
  231. (values (build-response #:code 404)
  232. (string-append "Resource not found: "
  233. (uri->string (request-uri request)))))
  234. (define (test-form)
  235. (respond
  236. `((div (@ (class "container"))
  237. (div (@ (class "row")))
  238. (div (@ (class "col-md-12"))
  239. (form (@ (method "post")
  240. (action "submit1")
  241. (id "test-form"))
  242. ,(horizontal-form-group "Your first name" "first-name"
  243. #:placeholder "Jason")
  244. ,(horizontal-form-group "Your last name" "last-name"
  245. #:placeholder "Smith")
  246. ,(horizontal-form-group "Your email" "email"
  247. #:placeholder "youremail@gmail.com"
  248. #:input-type "email")
  249. (input (@ (name "hidden") (hidden)))
  250. (div (@ (class "row"))
  251. (div (@ (class "col-md-2"))
  252. (input (@ (class "btn btn-primary")
  253. (type "submit")
  254. )
  255. "Submit")))
  256. ))))))
  257. (define (main-page)
  258. (respond
  259. `((div (@ (class "container"))
  260. (div (@ (class "row"))
  261. (div (@ (class "col-md-12"))
  262. (h1 "Apply for a loan")
  263. (form (@ (method "post")
  264. (action "submit"))
  265. ,(horizontal-form-group "Your first name" "first-name"
  266. #:placeholder "James")
  267. ,(horizontal-form-group "Your last name" "last-name"
  268. #:placeholder "Jones")
  269. ,(horizontal-form-group "Your number" "number"
  270. ;;TODO
  271. ;; I want to format this as a number via
  272. ;;<input type="tel" name="phone" pattern="[0-9]{3}-[0-9]{2}-[0-9]{3}">
  273. ;; as seen here: https://www.w3schools.com/html/html_form_input_types.asp
  274. ;;#:input-type "tel"
  275. #:placeholder "765 293 4930" )
  276. ,(horizontal-form-group "Your email" "email"
  277. #:placeholder "youremail@gmail.com"
  278. #:input-type "email")
  279. ,(horizontal-form-group "Address" "address1"
  280. #:placeholder "123 Main Street")
  281. ,(horizontal-form-group "Address Line 2" "address1"
  282. #:placeholder "123 Main Street")
  283. (div (@ (class "form-row"))
  284. ,(basic-form-group "City" "city" #:width 4)
  285. ,(basic-form-group "State" "state" #:width 2)
  286. ,(basic-form-group "Zip" "zip" #:width 2)
  287. )
  288. ,(horizontal-form-group "Are you a U.S. Citizen?" "state"
  289. #:input-type "select"
  290. #:options '("No" "Yes"))
  291. ,(basic-form-group "Co-Borrower's (If applicable)" "co-borrowers"
  292. #:required #f)
  293. ,(basic-form-group "Borrower's Fico Score? (Please list all borrowers scores)"
  294. ;#:placeholder "700, 750, 800"
  295. "fico")
  296. ,(basic-form-group "Company name the borrower is closing in? (must close in a corporate entity)"
  297. "company")
  298. ;;This is my honeypot input. If a user puts data in this, then they are probably not a user.
  299. (input (@ (name "hidden") (hidden)))
  300. ,(basic-form-group "Who makes up the entity and what are their percentages of ownership?"
  301. "percentages" #:input-type "textarea")
  302. (div (@ (class "form-row"))
  303. ,(basic-form-group "Do you own any other investment properties?"
  304. "own"
  305. #:width 6)
  306. ,(basic-form-group "If yes, how many?"
  307. "own-number"
  308. #:width 2
  309. #:required #f))
  310. ,(basic-form-group
  311. "Does the borrower have any Tax liens, judgments, past bankruptcies, past chapter filings, past foreclosures, recent or pending lawsuits against them?"
  312. "past-problems"
  313. #:input-type "select"
  314. #:options '("No" "Yes"))
  315. ,(basic-form-group "If YES, please explain and list date(s)"
  316. "past-problems-reasons"
  317. #:input-type "textarea" #:required #f)
  318. ,(basic-form-group "Do you rent or own?" "rent-or-own"
  319. #:input-type "select" #:options '("Own" "Rent"))
  320. ,(basic-form-group "Have you ever had any late rent payments/mortgage?"
  321. "late-payments"
  322. #:input-type "select" #:options '("No" "Yes"))
  323. ,(basic-form-group "If yes when?" "late-payments-reasons" #:required #f)
  324. ,(basic-form-group "Are you already working with another broker or lender?"
  325. "other-lender"
  326. #:input-type "select" #:options '("No" "Yes"))
  327. ,(basic-form-group "If yes, who?" "other-lender-name" #:required #f)
  328. ,(basic-form-group "Are you currently working with an 11 Capital Finance IAP?"
  329. "11-capital-lender"
  330. #:input-type "select" #:options '("Yes" "No"))
  331. ,(basic-form-group "If yes, who?" "11-capital-lender-name" #:required #f)
  332. ,(basic-form-group "Are you or any member of the borrowing entity related by blood or marriage?"
  333. "related-borrowers"
  334. #:input-type "select" #:options '("No" "Yes"))
  335. ,(basic-form-group "What rates and terms are you expecting?"
  336. "rates-and-terms"
  337. #:input-type "textarea")
  338. ;; this is the second page
  339. ,(horizontal-form-group "Property Address" "address1"
  340. #:placeholder "123 Main Street")
  341. ,(horizontal-form-group "Property Address Line 2" "address1"
  342. #:placeholder "123 Main Street")
  343. ,(basic-form-group
  344. "Exact property type? eg: SFR, 2unit, 7unit: (If commercial property please be very specific."
  345. "exact-property-type")
  346. ,(basic-form-group "What is the property square footage?: (If applicable)"
  347. "footage"
  348. #:required #f)
  349. ,(basic-form-group "Loan type" "loan-type"
  350. #:input-type "select"
  351. #:options '("Permanent Finance"
  352. "Bridge Loan"
  353. "Rehab Loan"
  354. "Ground up Construction"))
  355. ;; this is the third page
  356. ,(basic-form-group "Purchase price" "purchase-price")
  357. ,(basic-form-group "Current fair market value of the property" "fair-market-value")
  358. ,(basic-form-group "Are you already in a purchase and sales contract" "purchase-or-sales-contract")
  359. ,(basic-form-group (string-append "How much money do you have to contribute towards "
  360. "the transaction? (Most commercial purchases require 30% down."
  361. "Borrower also needs to cover closing costs.")
  362. "money")
  363. ,(basic-form-group "Total cash on hand?" "cash-on-hand")
  364. ,(basic-form-group "What is the loan amount requested? In USD?" "loan-money")
  365. ,(basic-form-group "When does the borrower need to close?" "closing date")
  366. ,(basic-form-group "Is the property owner occupied or a pure investment property?"
  367. "occupied-pure-investment-property"
  368. #:input-type "select" #:options '("Owner Occupied" "Pure Investment"))
  369. ,(basic-form-group "What is the monthly rental income on the property?"
  370. "monthly-rent")
  371. ,(basic-form-group "What is the occupancy percentage of the property? (%)"
  372. "occupancy-percentage" #:placeholder "100%")
  373. ,(basic-form-group "What are the monthly taxes on the property? In USD?"
  374. "monthly-taxes")
  375. ,(basic-form-group "What is the insurance on the property?"
  376. "property-insurance")
  377. ,(basic-form-group "If the property type is a condo, is this a warrantable or non-warrantable condo?"
  378. "warrantable-or-not"
  379. #:required #f
  380. #:input-type "select" #:options '("Warrantable" "Non-Warrantable"))
  381. ,(basic-form-group "If yes, what are the dues?" "dues"
  382. #:required #f)
  383. ,(basic-form-group "If yes, how are the dues paid? eg: monthly, quartly, yearly"
  384. "how-paid" #:required #f)
  385. ,(basic-form-group "What is specific about your deal?" "specific" #:input-type "textarea")
  386. (div (@ (class "row"))
  387. (div (@ (class "col-md-2"))
  388. (input (@ (class "btn btn-primary")
  389. (type "submit")))))
  390. )
  391. ))))))
  392. (define (run-page request body)
  393. ;;(display (request-path-components request))
  394. (let ([current-page (request-path-components request)])
  395. (cond [(equal? current-page '())
  396. (main-page)
  397. ;;(respond '((h1 "Are you ready to kick start your loan?")))
  398. ]
  399. [(equal? current-page '("hacker"))
  400. (respond '((h1 "Hello Hacker!")))]
  401. [(equal? current-page '("submit"))
  402. (respond (submit-response body))
  403. ;;(respond '((h1 "Thank you for submitting your request! We will reach out to you soon!")))
  404. ;; (if (eq? (verify-request (request)) #t)
  405. ;; (main-page))
  406. ]
  407. [(equal? current-page '("test-form"))
  408. (test-form)]
  409. [(equal? current-page '("submit1"))
  410. (if (verify-body body)
  411. (respond '((h1 "Your entered correct data")))
  412. (respond '((h1 "You did not enter correct data."))))]
  413. [(equal? current-page '("css" "bootstrap.min.css"))
  414. (values `((content-type . (text/css))
  415. (cache-control . (public))
  416. ;;(parse-header 'date ,(current-date))
  417. )
  418. (let ([port (open-file "css/bootstrap.min.css" "r")])
  419. (define css-file (get-string-all port))
  420. (close-port port)
  421. css-file))]
  422. [(equal? current-page '("apply")
  423. (main-page)
  424. )]
  425. [(equal? current-page '("hello"))
  426. (values `((content-type . (text/plain))
  427. )
  428. "Hello hacker!")]
  429. [else
  430. (respond `((h1 "Page not found.")
  431. (h1 ,(let loop ([current-page current-page])
  432. (if (null? current-page) ""
  433. (string-append (car current-page) "/"
  434. (loop (cdr current-page))))))))])))
  435. (run-server run-page 'http '(#:port 8081) ; '(#:host="localhost")
  436. )