aboutsummaryrefslogtreecommitdiffstats
path: root/webstats/server.lisp
blob: 3fe237a5aa6f033224ab2183394f3512d07f56b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
(defpackage :webstats
  (:use :common-lisp :hunchentoot :spinneret))

(defpackage :webstats-js
  (:use :cl :parenscript))

(in-package :webstats-js)
(setf *js-target-version* "1.9")

(ps::define-statement-operator for-of ((var iterable) &rest body)
  `(ps-js::for-of ,(ps::compile-expression var)
     ,(ps::compile-expression iterable)
     ,(ps::compile-loop-body (list var) body)))

(ps::defprinter ps-js::for-of (var object body-block)
                "for (const "(ps::ps-print var)" of "(ps::ps-print object)") "
                (ps::ps-print body-block))

(in-package :webstats)


(yesql:import log-queries
  :from "queries.sql"
  :as :cl-yesql/sqlite
  :binding :all-functions)

;; (sqlite:with-open-database (db "test.db")
;;   (drop-stats-table db)
;;   (create-stats-table db))

;; (sqlite:with-open-database (db "test.db")
;;   (insert db
;;           :click nil
;;           :page "ho"
;;           :referer "ref"
;;           :ip "13"
;;           :user-agent "sly"
;;           :title "try"))

(hunchentoot:define-easy-handler
    (visit :uri "/visit" :default-request-type :both)
    (title page referer click)
  (format nil "you are our visit ~d"
          (insert *sqlite*
                  :click click
                  :page page
                  :referer referer
                  :ip (remote-addr*)
                  :user-agent (user-agent)
                  :title title)))


(hunchentoot:define-easy-handler (stat-js :uri "/stats.js") ()
  (setf (hunchentoot:content-type*) "text/javascript")
  (ps:ps-compile-file "stats.paren"))

(hunchentoot:define-easy-handler (metric :uri "/metric.json") (q)
  (setf (hunchentoot:content-type*) "application/json")
  (cl-json:encode-json-to-string
   (apply #'mapcar #'list
          (cond
            ((string= q "all")
             (activity-stats *sqlite*))
            ((string= q "split")
             (activity-places *sqlite*))
            ((list (list)))))))

(hunchentoot:define-easy-handler (graphs :uri "/graphs") ()
  (with-html-string
    (:doctype)
    (:html
     (:head (:title "hu yu ipi")
            (:meta :charset "utf-8")
            (:link :rel "stylesheet" :href "/webstats/static/uPlot.min.css")
            (:script :async t :src "/webstats/static/uPlot.iife.min.js" :type "text/javascript")
            (:script :async t :src "/stats/stats.js" :type "text/javascript")
            (:script :async t :src "http://127.0.0.1:8095/skewer"))
     (:body
      (:h1 "great graph stats")
      (:div :id "graph")
      (:script
       (:raw
        (ps:ps
          (add-event-listener
           "load"
           (lambda ()
             (ps:chain (fetch "/stats/metric.json?q=all")
                       (then #'response-to-json)
                       (then #'plot)))))))))))

(defvar *acceptor*)
(defvar *sqlite*)

(defun start-server (port)
  (setf *acceptor*
        (make-instance 'hunchentoot:easy-acceptor :port port))
  (setf *sqlite* (sqlite:connect "test.db"))
  (hunchentoot:start *acceptor*))

(defun main ()
  (let ((port (parse-integer (or (uiop:getenv "PORT")
                                 "4252"))))
    (start-server port)
    (format *standard-output* "Hunchentoot server started on port ~d.~&" port)
    (handler-case (bt:join-thread
                   (find-if (lambda (th)
                              (search "hunchentoot" (bt:thread-name th)))
                            (bt:all-threads)))
      ;; Catch a user's C-c
      (#+sbcl sb-sys:interactive-interrupt
       #+ccl  ccl:interrupt-signal-condition
       #+clisp system::simple-interrupt-condition
       #+ecl ext:interactive-interrupt
       #+allegro excl:interrupt-signal
       () (progn
            (format *error-output* "Aborting.~&")
            (hunchentoot:stop *acceptor*)
            (sqlite:disconnect *sqlite*)
            (uiop:quit)))
      (error (c) (format t "Woops, an unknown error occured:~&~a~&" c)))))

(defun create-static-assets ()
  (let ((ps:*ps-print-pretty* nil))
    (with-open-file (ps:*parenscript-stream*
                     "stats.js" :direction :output
                     :if-exists :supersede)
      (ps:ps-compile-file "stats.paren"))))