Innocenty Enikeew 8 жил өмнө
commit
88ffe854ce
3 өөрчлөгдсөн 187 нэмэгдсэн , 0 устгасан
  1. 4 0
      package.lisp
  2. 19 0
      travels.asd
  3. 164 0
      travels.lisp

+ 4 - 0
package.lisp

@@ -0,0 +1,4 @@
+(defpackage #:travels
+  (:use :cl))
+
+(in-package #:travels)

+ 19 - 0
travels.asd

@@ -0,0 +1,19 @@
+(in-package :cl-user)
+(defpackage travels-asd
+  (:use :cl :asdf))
+(in-package :travels-asd)
+
+(defsystem travels
+  :version "0.1"
+  :author "Innokenty Enikeev"
+  :license ""
+  :depends-on (:clack
+               :lack
+               :quri
+               :zip
+               :flexi-streams
+               :myway
+               :jonathan)
+  :serial t
+  :components ((:file "package")
+               (:file "travels")))

+ 164 - 0
travels.lisp

@@ -0,0 +1,164 @@
+(in-package :travels)
+
+(defun update-indexes (storage old new)
+  (let ((id (or (getf old :|id|) (getf new :|id|)))
+        (old-user (getf old :|user|))
+        (new-user (getf new :|user|))
+        (old-loc (getf old :|location|))
+        (new-loc (getf new :|location|)))
+    (labels ((update (s o n)
+               (when o (setf (gethash o s) (remove id (gethash o s))))
+               (when n (push id (gethash n s)))))
+      (when (or (null new)
+                (null old)
+                (and new-user (not (= old-user new-user))))
+        (update (or (gethash :user-visits storage)
+                    (setf (gethash :user-visits storage) (make-hash-table)))
+                old-user new-user))
+      (when (or (null new) (null old)
+                (and new-loc (not (= old-loc new-loc))))
+        (update (or (gethash :location-visits storage)
+                    (setf (gethash :location-visits storage) (make-hash-table)))
+                old-loc new-loc)))))
+
+(defun load-data (pathname)
+  (let ((storage (make-hash-table)))
+    (zip:with-zipfile (zip pathname)
+      (zip:do-zipfile-entries (name entry zip)
+        (let ((data (jojo:parse
+                     (flex:octets-to-string (zip:zipfile-entry-contents entry) :external-format :utf-8)
+                     :as :plist)))
+          (destructuring-bind (entity elements) data
+            (let ((entity-storage
+                   (or (gethash entity storage)
+                       (setf (gethash entity storage)
+                             (make-hash-table :size (length elements))))))
+              (loop for item in elements
+                 do (setf (gethash (getf item :|id|) entity-storage) item)
+                 when (eq entity :|visits|)
+                 do (update-indexes storage nil item)))))))
+    storage))
+
+(defvar *storage* nil "data store")
+
+(let ((keyword-package (find-package :keyword)))
+  (defun getkey (place indicator)
+    (declare
+     (optimize (speed 3) (safety 0))
+     (type list place)
+     (type symbol indicator))
+    (intern (getf place indicator) keyword-package)))
+
+(defparameter +400+ '(400 nil nil))
+(defparameter +404+ '(404 nil nil))
+(defparameter +200-empty+ '(200 (:content-type "application/json") ("{}")))
+
+(defun get-entity (params)
+  (let ((id (parse-integer (getf params :id) :junk-allowed t))
+        (entity (getkey params :entity)))
+    (if id
+        (case entity
+          ((:|users| :|visits| :|locations|)
+           (let ((entity (gethash id (gethash entity *storage*))))
+             (if entity
+                 `(200 (:content-type "application/json") (,(jojo:to-json entity)))
+                 +404+)))
+          (otherwise +404+))
+        +400+)))
+
+(defun post-entity (params)
+  (let ((id (parse-integer (getf params :id) :junk-allowed t))
+        (entity (getkey params :entity)))
+    (if (or id (string= (getf params :id) "new"))
+        (case entity
+          ((:|users| :|visits| :|locations|)
+           (handler-case
+               (let ((body (make-string (getf myway:*env* :content-length))))
+                 (read-sequence body (flex:make-flexi-stream
+                                      (getf myway:*env* :raw-body)
+                                      :external-format :utf8))
+                 (let ((item (jojo:parse body))
+                       (entity-storage (gethash entity *storage*)))
+                   (if id
+                       (let ((existing (gethash id entity-storage)))
+                         (if existing
+                             (progn
+                               (when (eq entity :|visits|)
+                                 (update-indexes *storage* existing item))
+                               (loop for (k v) on item by #'cddr
+                                  unless (eq k :|id|)
+                                  do (setf (getf existing k) v))
+                               +200-EMPTY+)
+                             +404+))
+                       (progn
+                         (when (eq entity :|visits|)
+                           (update-indexes *storage* nil item))
+                         (setf (gethash (getf item :|id|) entity-storage) item)
+                         +200-EMPTY+))))
+             (error () +400+)))
+          (otherwise +404+))
+        +400+)))
+
+(defun aget (place indicator &key (test #'equal))
+  (declare (type list place)
+           (type (or string symbol) indicator)
+           (type function test)
+           (optimize (speed 3) (safety 0)))
+  (cdr (assoc indicator place :test test)))
+
+(defun may-integer (string)
+  (declare (type (or null string) string))
+  (when string
+    (parse-integer string)))
+
+(defun matching-location (location country to-distance)
+  (and (or (not country) (equal (getf location :|country|) country))
+       (or (not to-distance) (< (getf location :|distance|) to-distance))))
+
+(defun matching-visit (visit from-date to-date)
+  (and (or (not from-date) (> (getf visit :|visited_at|) from-date))
+       (or (not to-date) (< (getf visit :|visited_at|) to-date))))
+
+
+(defun user-visits (params)
+  (let ((id (parse-integer (getf params :id) :junk-allowed t)))
+    (if id
+        (let ((user (gethash id (gethash :|users| *storage*))))
+          (if user
+              (handler-case
+                  (let* ((user-visit-ids (gethash id (gethash :user-visits *storage*)))
+                         (locations (gethash :|locations| *storage*))
+                         (visits (gethash :|visits| *storage*))
+                         (query-string (getf myway:*env* :query-string))
+                         (query-params (and query-string (quri:url-decode-params query-string)))
+                         (from-date (may-integer (aget query-params "fromDate")))
+                         (to-date (may-integer (aget query-params "toDate")))
+                         (to-distance (may-integer (aget query-params "toDistance")))
+                         (country (aget query-params "country"))
+                         (user-visits (loop for v-id in user-visit-ids
+                                         for visit = (gethash v-id visits)
+                                         for loc = (gethash (getf visit :|location|) locations)
+                                         when (and visit (matching-visit visit from-date to-date))
+                                         when (and loc (matching-location loc country to-distance))
+                                         collect (list :|mark| (getf visit :|mark|)
+                                                       :|visited_at| (getf visit :|visited_at|)
+                                                       :|place| (getf loc :|place|)
+                                                       :|loc| loc))))
+                    (sort user-visits #'< :key (lambda (v) (getf v :|visited_at|)))
+                    `(200 (:content-type "application/json") (,(jojo:to-json (list :|visits| user-visits)))))
+                (error () +400+))
+              +404+))
+        +400+)))
+
+(defvar *mapper* (myway:make-mapper))
+(myway:connect *mapper* "/:entity/:id" 'post-entity :method :post)
+(myway:connect *mapper* "/:entity/:id" 'get-entity)
+(myway:connect *mapper* "/users/:id/visits" 'user-visits)
+
+(defun main ()
+  (setf *storage* (load-data "data.zip"))
+  (clack:clackup (myway:to-app *mapper*)
+   :server :woo
+   :debug t
+   :use-default-middlewares nil
+   :use-thread nil))