This commit is contained in:
2026-02-17 18:54:08 -05:00
parent f7e2755a91
commit 7fd8d7c4eb
17 changed files with 293 additions and 874 deletions
-22
View File
@@ -1,22 +0,0 @@
# API Service Plan
## Stack
- http-kit (HTTP server)
- reitit (routing) + Ring middleware
- next.jdbc + HoneySQL (via shared/)
- PostgreSQL everywhere (dev + prod)
## Responsibilities
- Stateless REST API: all reads & writes for messages, channels, users
- Only service (besides Auth GW) with a direct PG connection
- Publishes events to NATS after DB writes
- No live connections — pure request/response
- Sits behind auth gateway (all requests pre-authenticated)
## TODO
- [ ] Define reitit route table
- [ ] http-kit server setup
- [ ] Design message/channel/user API endpoints
- [ ] Ring middleware: error handling, content negotiation, request logging
- [ ] NATS publish on write (message sent, channel created, etc.)
- [ ] Database migrations
+29 -23
View File
@@ -42,9 +42,10 @@
(defn- get-message-row [ds message-id]
(or (db/execute-one! ds
{:select [:*]
:from [:messages]
:where [:= :id [:cast message-id :uuid]]})
{:select [:m.* :u.username :u.display-name :u.avatar-url]
:from [[:messages :m]]
:join [[:users :u] [:= :m.user-id :u.id]]
:where [:= :m.id [:cast message-id :uuid]]})
(throw (ex-info "Message not found" {:type :ajet.chat/not-found}))))
(defn- nats-subject-for-channel
@@ -140,19 +141,19 @@
(check-channel-member! ds channel-id user-id)
(let [base-where [:= :channel-id [:cast channel-id :uuid]]
(let [base-where [:= :m.channel-id [:cast channel-id :uuid]]
;; Cursor-based pagination using subquery on created_at
where-clause (cond
before
[:and base-where
[:< :created-at
[:< :m.created-at
{:select [:created-at]
:from [:messages]
:where [:= :id [:cast before :uuid]]}]]
after
[:and base-where
[:> :created-at
[:> :m.created-at
{:select [:created-at]
:from [:messages]
:where [:= :id [:cast after :uuid]]}]]
@@ -160,10 +161,11 @@
:else base-where)
order-dir (if after :asc :desc)
messages (db/execute! ds
{:select [:*]
:from [:messages]
{:select [:m.* :u.username :u.display-name :u.avatar-url]
:from [[:messages :m]]
:join [[:users :u] [:= :m.user-id :u.id]]
:where where-clause
:order-by [[:created-at order-dir]]
:order-by [[:m.created-at order-dir]]
:limit limit})
;; Always return newest-last ordering
messages (if (= order-dir :desc)
@@ -213,19 +215,22 @@
(when resolved-parent-id
(create-thread-notifications! tx message-id resolved-parent-id user-id)))
;; Publish event
;; Publish event with user info for real-time display
(let [channel (get-channel-row ds channel-id)
subject (nats-subject-for-channel channel)
message (get-message-row ds message-id)]
message (get-message-row ds message-id)
user (db/execute-one! ds
{:select [:username :display-name :avatar-url]
:from [:users]
:where [:= :id [:cast user-id :uuid]]})]
(publish-event! nats subject :message/created
{:message-id message-id
:channel-id channel-id
:user-id user-id
:body-md body-md
:parent-id resolved-parent-id
:community-id (when (:community-id channel)
(str (:community-id channel)))})
(merge message
{:username (:username user)
:display-name (:display-name user)
:avatar-url (:avatar-url user)
:community-id (when (:community-id channel)
(str (:community-id channel)))}))
(mw/json-response 201 message)))))
@@ -360,12 +365,13 @@
(check-channel-member! ds (str (:channel-id message)) user-id)
;; Get root message + all replies
;; Get root message + all replies (with user info)
(let [replies (db/execute! ds
{:select [:*]
:from [:messages]
:where [:= :parent-id [:cast message-id :uuid]]
:order-by [[:created-at :asc]]})]
{:select [:m.* :u.username :u.display-name :u.avatar-url]
:from [[:messages :m]]
:join [[:users :u] [:= :m.user-id :u.id]]
:where [:= :m.parent-id [:cast message-id :uuid]]
:order-by [[:m.created-at :asc]]})]
(mw/json-response {:root message
:replies replies}))))