clj-forum
clj-forum copied to clipboard
a forum built with Datomic and Clojure
clj-forum
~~Live Demo~~ (Offline)
I'm figuring things out and yak-shaving as I go.
(I'm particularly inexperienced with Datomic)
Features
Implemented/somewhat implemented:
- Conventional forums <-- topics <-- posts relationship
- Roles:
:admin,:mod,:member,:banned,:guest - Avatars. Users are randomly generated one upon registration (randomly-colored square)
- Admin/mod/users share a single interface. http://gettingreal.37signals.com/ch09_One_Interface.php
- Post permalinks
- Debug toolbar. Ex: Shows the full
requestmap - Live Markdown preview on New Topic and New Post forums (same Markdown parser on server and client)
- CSRF protection
Planned:
- Private messages
- Users able to "heart"/+1 posts
@<Username>mentions to summon a user- Notifications for new hearts, private messages, and
@mentions - "Report Post" button so users can notify admins/mods
- Embedded BBCode for support for things like font colors and richer formatting
Screenshots
Homepage (Forum index):

Live Markdown preview:

Moderator tools:

Debug toolbar:

Running it
Start the transactor
-
Download latest Datomic-free
-
cdinto it -
Uncomment the dev laptop settings in
config/samples/free-transactor-template.properties -
Run the transactor in its own terminal:
bin/transactor -Xmx1g config/samples/free-transactor-template.properties
Start clj-forum
git clone [email protected]:danneu/clj-forum.gitcd clj-forumlein ring server
(Run tests with lein autoexpect)
Implementation
The flow:
- Routes in
forum.handlersend params to a controller inforum.controllers.*. - A controller make DB calls and (unimplemented:) authorizes access, then passes values to a view in
forum.views.*. - Views are just Hiccup datastructures.
Middleware:
forum.middleware.expose-requestbinds a dynamic varreqthat any namespace can refer to if they want to see the full Ring request-map. Ideally,reqwill be broken up into more specific functions/vars sort of like this next middleware:forum.middleware.wrap-current-userbinds a dynamic varcurrent-userthat either contains the DB entity of a user ({:user/uname "kate", :user/digest "abc", :user/topics [...], :user/posts [...], ...}) ornilif there is no user logged in. It reads this data from the session.
Session:
forum.controllers.sessionsis the controller used in the login/logout flow. It creates a session or sets it to nil.- The session is stored in an encrypted cookie that's signed by an application-unique
:session-secretthat should be defined inresources/config.edn. The secret should be >= 16 bytes (minimum necessary for the AES key).
Database attributes:
User
- :user/uname - username
- :user/digest - plaintext salt concatenated with hashed password (then converted to base64)
- :user/topics - refs of the topics the user has created
- :user/posts
- :user/created - when user was created
- :user/uid - unique id of user that's autoincremented on creation. the preferred lookup attribute.
- :user/role - either :admin or :mod (:member and :guest are not stored in the database. A :member is
{:user/uid _, ...}without a given role. A :guest is a user with no :user/uid -- a user that's not saved in the database.)
Forum
- :forum/title
- :forum/desc - description
- :forum/topics
- :forum/uid
Topic
- :topic/title
- :topic/posts
- :topic/uid
- :topic/created
The OP's post in a topic is simply the first post (sorted by uid ascending). A topic is just a titled collection of posts.
Post
- :post/text
- :post/uid
- :post/created
TODO
- Add :user/email validation
- Use form validation from https://github.com/brentonashworth/sandbar