diff options
author | dan <[email protected]> | 2023-03-08 16:37:47 -0500 |
---|---|---|
committer | dan <[email protected]> | 2023-03-08 16:37:47 -0500 |
commit | 1b63fdedb55c1c652c8a46afce73865ad46861f2 (patch) | |
tree | 8c0c7ebed8511742b85ca4acde9727f377b4fa9a | |
parent | 8b1ae4b43a59fb76f3628e0f6674b25cc632a364 (diff) | |
download | 54-1b63fdedb55c1c652c8a46afce73865ad46861f2.tar.gz 54-1b63fdedb55c1c652c8a46afce73865ad46861f2.tar.bz2 54-1b63fdedb55c1c652c8a46afce73865ad46861f2.zip |
feat: posts can have an image
-rw-r--r-- | main.scm | 124 | ||||
-rw-r--r-- | nginx.conf | 1 | ||||
-rw-r--r-- | style.css | 16 |
3 files changed, 106 insertions, 35 deletions
@@ -1,11 +1,12 @@ (import scheme (chicken base) (chicken format) (chicken port) (chicken io) (chicken random) (chicken time) - (chicken process-context) (chicken irregex) (chicken condition) - spiffy intarweb uri-common html-parser spiffy-request-vars + (chicken process-context) (chicken irregex) (chicken condition) (chicken blob) + spiffy intarweb uri-common html-parser spiffy-request-vars multipart-form-data sqlite3 sql-null srfi-69 ;hash tables srfi-1 ;list functions + srfi-4 ;u8vector ) @@ -19,7 +20,8 @@ user_id TEXT, content TEXT, created_at DATETIME default current_timestamp, - parent_id TEXT + parent_id TEXT, + jpeg_image BLOB )") (execute db "CREATE TABLE flush_votes ( user_id TEXT PRIMARY KEY, @@ -31,13 +33,19 @@ (+ (pseudo-random-integer 65536) (* (current-seconds) 100000))) -(define (create-post user content parent-id) - (let ([id (gen-id)]) - (if (and parent-id (not (equal? "" parent-id))) - (execute db "insert into posts (id, user_id, content,parent_id) values (?, ?, ?, ?)" - id user content parent-id) - (execute db "insert into posts (id, user_id, content) values (?, ?, ?)" - id user content)))) +(define (create-post user content parent-id image) + + (let ([id (gen-id)] + [parent-id-or-null (if (or (not parent-id) (eof-object? parent-id) (equal? "" parent-id)) + (sql-null) + parent-id)] + [image-or-null (if (or (not image) (eof-object? image) (equal? "" image)) + (sql-null) + image)]) + (execute db "insert into posts + (id, user_id, content, parent_id, jpeg_image) + values (?, ?, ?, ?, ?)" + id user content parent-id-or-null image-or-null))) (define (vote-to-flush user) (execute db "insert or ignore into flush_votes (user_id) values (?)" @@ -61,16 +69,16 @@ ; (read-u8vector len port))) (define (top-posts) (map-row list db - "SELECT p.id, p.user_id, p.content, p.created_at, p.parent_id, count(c.id) + "SELECT p.id, p.user_id, p.content, p.created_at, p.parent_id, count(c.id), p.jpeg_image is not null as has_image FROM posts p LEFT JOIN posts c ON c.parent_id = p.id WHERE p.parent_id IS NULL GROUP BY p.id, p.user_id, p.content, p.created_at, p.parent_id ORDER BY count(c.id) DESC, p.created_at DESC LIMIT 25")) - + (define (get-post post-id) (condition-case (first-row db - "SELECT p.id, p.user_id, p.content, p.created_at, p.parent_id, count(c.id) + "SELECT p.id, p.user_id, p.content, p.created_at, p.parent_id, count(c.id), p.jpeg_image is not null as has_image FROM posts p LEFT JOIN posts c ON c.parent_id = p.id WHERE p.id = ? @@ -79,9 +87,18 @@ post-id) [(exn sqlite3) #f])) +(define (get-post-image post-id) + (condition-case (first-result db + "SELECT p.jpeg_image + FROM posts p + WHERE p.id = ?" + post-id) + [(exn sqlite3) #f])) + + (define (get-child-posts post-id) (map-row list db - "SELECT p.id, p.user_id, p.content, p.created_at, p.parent_id, count(c.id) + "SELECT p.id, p.user_id, p.content, p.created_at, p.parent_id, count(c.id), p.jpeg_image is not null as has_image FROM posts p LEFT JOIN posts c ON c.parent_id = p.id WHERE p.parent_id = ? @@ -90,7 +107,7 @@ (define (get-user-posts user-id) (map-row list db - "SELECT p.id, p.user_id, p.content, p.created_at, p.parent_id, count(c.id) + "SELECT p.id, p.user_id, p.content, p.created_at, p.parent_id, count(c.id), p.jpeg_image is not null as has_image FROM posts p LEFT JOIN posts c ON c.parent_id = p.id WHERE p.parent_id IS NULL AND p.user_id = ? @@ -115,6 +132,7 @@ (define (created-at p) (fourth p)) (define (parent-id p) (fifth p)) (define (children-count p) (sixth p)) +(define (has-image? p) (= 1 (seventh p))) (define users (alist->hash-table '(("lawrence" . "pw") ("demo" . "pw") ("dan" . "pw")))) @@ -138,9 +156,10 @@ (define (get-prev-path) (get-cookie "prev")) -(define (post-path id) +(define (post-path id) (if (and id + (not (eof-object? id)) (not (sql-null? id)) (not (equal? "" id))) (string-append "/posts/" id) @@ -156,9 +175,11 @@ ) +(define (get-opt opt opts) (find (cut equal? <> opt) opts)) + (define (post current-user p . opts) (let ([uri (post-path (id p))] - [hide-comments-link (find (cut equal? <> 'hide-comments-link) opts)]) + [hide-comments-link (get-opt 'hide-comments-link opts)]) `(div (@ [class "post"] [id ,(id p)]) ,(if (equal? current-user (user p)) `(a (@ [href ,(string-append @@ -168,6 +189,11 @@ [class "delete-button"]) "[delete]") '()) + ,(if (has-image? p) + `(img (@ [src ,(string-append "/posts/image/" (id p))] + [style "max-width:100%;"] + [loading "lazy"])) + '()) (p (@ [style "min-height:0.5em;white-space:pre;"]) ,(content p)) (div [@ (class "post-info")] (em "- " (a (@ [href ,(user-path (user p))]) ,(user p))) @@ -178,21 +204,31 @@ ))))) (define (new-post-form parent-id) - `(form (@ [class "new-post-form"] [action "/"] [method "POST"]) + `(form (@ [class "new-post-form"] + [action "/"] + [method "POST"] + [enctype "multipart/form-data"]) (input (@ [type "hidden"] [id "parent_id"] [name "parent_id"] [value ,(or parent-id "")])) - (span + (span (textarea (@ [maxlength "540"] [id "content"] [name "content"] [value ""] [rows "5"] [cols "50"]))) - (input (@ [id "submit"] [type "submit"] [value "Post"])))) - -(define (page inner) + (input (@ [id "submit"] [type "submit"] [value "Post"])) + (label (@ [for "image"] [style "margin-left:1em"]) Image:) + (input (@ [type "file"] + [id "image"] + [accept "image/jpeg"] + [name "image"] + [style "margin-left:0.5em"])) + )) + +(define (page inner) (with-output-to-string (lambda () (sxml-display-as-html `(html (head @@ -298,10 +334,6 @@ (define (get-next-path) (get-qp-path 'next)) - -;; Use cookie instead??? - - (define allowed-return-paths '("^/posts/[0-9]*$" "^/users/[A-z0-9_-]*$")) @@ -312,27 +344,32 @@ p "/"))) - -;(define (set-prev-cookie) `(set-cookie #(("prev" . ,(current-request-path-string)) ()))) +(define (get-image-bytes image-multipart) + (and (multipart-file? image-multipart) + (u8vector->blob (read-u8vector #f (multipart-file-port image-multipart))))) (define routes `( (GET (/ "style.css") ,(lambda (rt) (send-static-file "style.css"))) + (GET (/ "favicon.ico") ,(lambda (rt) (send-static-file "favicon.ico"))) + (GET (/ "unvote") ,(lambda (rt) (unvote (get-current-user)) (send-response headers: `((location ,(get-next-path))) status: 'see-other ))) + (GET (/ "vote-to-flush") ,(lambda (rt) (let ([user (get-current-user)]) (when user (vote-to-flush user))) (send-response headers: `((location ,(get-next-path))) status: 'see-other ))) + (GET (/ "login") ,(lambda (rt) (send-response headers: '((content-type text/html)) status: 'ok @@ -340,6 +377,7 @@ (a (@ [style "float:right;"] [href "/"]) back) (h2 "Login") ,login-form))))) + (POST (/ "login") ,(lambda (rt) (let* ([form-data (read-urlencoded-request-data (current-request))] [username (cdr (assv 'username form-data))] @@ -350,6 +388,7 @@ (set-cookie #(("apikey" . ,apikey) ()))) status: 'see-other)))) + (GET (/ "logout") ,(lambda (rt) (send-response headers: `((location "/") @@ -357,17 +396,21 @@ #(("apikey" . "") ()))) ; should also wipe server-side keys status: 'see-other ))) + (POST (/ "") ,(lambda (rt) - (let* ([form-data (read-urlencoded-request-data (current-request))] + (let* ([form-data (read-multipart-form-data (current-request))] [content (cdr (assv 'content form-data))] [parent-id (cdr (assv 'parent_id form-data))] + [image-multipart (cdr (assv 'image form-data))] + [image (get-image-bytes image-multipart)] [user (get-current-user)] - [success? (if user (create-post user content parent-id) #f)]) + [success? (and user (create-post user content parent-id image))]) (send-response headers: `((location ,(post-path parent-id))) status: 'see-other )))) - (GET (/ "") ,(lambda (rt) + + (GET (/ "") ,(lambda (rt) (let ([current-user (get-current-user)] [displayed-posts (top-posts)]) (send-response @@ -384,20 +427,31 @@ status: 'ok body: (user-page current-user user-id posts))))) - (GET (/ "posts" ":id") ,(lambda (rt) + (GET (/ "posts" "image" ":id") ,(lambda (rt) + (let* ([post-id (get-route-param rt "id")] + [post-image (and post (get-post-image post-id))] + [post-image-string (and post-image (blob->string post-image))]) + (if post-image-string + (send-response + headers: '((content-type image/jpeg)) + status: 'ok + body: post-image-string) + (send-status 'not-found "This image no longer exists!"))))) + + (GET (/ "posts" ":id") ,(lambda (rt) (let* ([current-user (get-current-user)] [post-id (get-route-param rt "id")] [post (get-post post-id)] [comments (if post (get-child-posts post-id) #f)]) (if post (send-response - headers: '((content-type text/html)) + headers: '((content-type text/html)) status: 'ok body: (post-page current-user post comments)) (send-status 'not-found "This Post no longer exists!"))))) ; Has method GET so that it can be used from <a> tag links - (GET (/ "posts" ":id" "delete") ,(lambda (rt) + (GET (/ "posts" ":id" "delete") ,(lambda (rt) (let* ([current-user (get-current-user)] [post-id (get-route-param rt "id")] [parent-id (delete-post post-id current-user)]) @@ -435,7 +489,7 @@ (uri-path uri) (second r)))) routes)) - + (define (handle continue) (let* ((uri (request-uri (current-request))) (method (request-method (current-request))) @@ -5,6 +5,7 @@ upstream itter { server { listen 443 ssl; server_name 54itter.dotemgo.com; + client_max_body_size 10m; ssl_certificate /root/dotemgo.com.pem.crt; ssl_certificate_key /root/dotemgo.com.pem.key; @@ -162,3 +162,19 @@ textarea { .navbar { height: 2em; } + +#image::file-selector-button { + border: 0; + line-height: 2.5; + padding: 0 20px; + font-size: 1rem; + text-align: center; + color: #fff; + text-shadow: 1px 1px 1px #000; + border-radius: 10px; + background-color: rgba(0, 0, 240, 0.6); + background-image: linear-gradient(to top left, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2) 30%, rgba(0, 0, 0, 0)); + box-shadow: inset 2px 2px 3px rgba(255, 255, 255, 0.6), inset -2px -2px 3px rgba(0, 0, 0, 0.6); + margin-top:2px; + cursor: pointer; +} |