Expanding the model and updating documentation.
This commit is contained in:
parent
e172d782b3
commit
ca2a85c81b
7 changed files with 152 additions and 39 deletions
|
@ -32,17 +32,17 @@ Comments may only include text.
|
|||
|
||||
## Relational Data Model
|
||||
|
||||
- [Table: `content`](#table-content)
|
||||
- [Table: `posts`](#table-posts)
|
||||
- [Table: `comments`](#table-comments)
|
||||
- [Table: `assets`](#table-assets)
|
||||
- [Table: `tags`](#table-tags)
|
||||
- [Table: `content_tags`](#table-content_tags)
|
||||
- [Table: `post_tags`](#table-post_tags)
|
||||
- [Table: `asset_tags`](#table-asset_tags)
|
||||
|
||||
### Table: `content`
|
||||
### Table: `posts`
|
||||
|
||||
```sql
|
||||
CREATE TABLE content(
|
||||
CREATE TABLE posts(
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
external_id UUID NOT NULL UNIQUE,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
|
@ -58,16 +58,18 @@ CREATE TABLE content(
|
|||
CREATE TABLE comments(
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
external_id UUID NOT NULL UNIQUE,
|
||||
post_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
created_by BIGINT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
contents TEXT NOT NULL,
|
||||
depth INT NOT NULL,
|
||||
parent BIGINT NULL,
|
||||
);
|
||||
```
|
||||
|
||||
Comments store their content directly. Comments are not complex, and are
|
||||
typically shorter than primary content. The practical limit for PostgreSQL is
|
||||
1gb per row, but ShortForm imposes a configurable limit (10kb by default).
|
||||
typically shorter than posts. The practical limit for PostgreSQL is 1gb per row,
|
||||
but ShortForm imposes a configurable limit (10kb by default).
|
||||
|
||||
### Table: `assets`
|
||||
|
||||
|
@ -95,13 +97,13 @@ CREATE TABLE tags(
|
|||
|
||||
Tags are arbitrary labels that authors may assign to top level posts and assets.
|
||||
|
||||
### Table: `content_tags`
|
||||
### Table: `post_tags`
|
||||
|
||||
```sql
|
||||
CREATE TABLE content_tags(
|
||||
content_id BIGINT NOT NULL,
|
||||
CREATE TABLE post_tags(
|
||||
post_id BIGINT NOT NULL,
|
||||
tag_id BIGINT NOT NULL,
|
||||
PRIMARY KEY(content_id, tag_id)
|
||||
PRIMARY KEY(post_id, tag_id)
|
||||
);
|
||||
```
|
||||
|
||||
|
|
|
@ -53,11 +53,11 @@ CREATE TYPE user_status AS ENUM ('active', 'locked', 'initializing');
|
|||
```sql
|
||||
CREATE TABLE users(
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL,
|
||||
role user_role NOT NULL,
|
||||
status user_status NOT NULL
|
||||
status user_status NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
|
@ -66,9 +66,9 @@ CREATE TABLE users(
|
|||
```sql
|
||||
CREATE TABLE password_resets(
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
token TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
token TEXT NOT NULL,
|
||||
used BOOLEAN NOT NULL
|
||||
);
|
||||
```
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package gs.shortform.model
|
||||
|
||||
import gs.uuid.UUID
|
||||
|
||||
/**
|
||||
* Represents a comment. All comments are associated to some [[Post]], but
|
||||
* may also be replies to other comments.
|
||||
*
|
||||
* Comments cannot currently be edited.
|
||||
*
|
||||
* @param externalId Globally unique identifier for this comment.
|
||||
* @param createdAt Instant this comment was created.
|
||||
* @param createdBy User who created this comment.
|
||||
* @param rendered The pre-rendered text contents of this comment.
|
||||
* @param depth The depth of this comment.
|
||||
* @param parent The parent comment of this comment.
|
||||
*/
|
||||
case class Comment(
|
||||
externalId: UUID,
|
||||
createdAt: CreatedAt,
|
||||
createdBy: Username,
|
||||
rendered: String,
|
||||
depth: Comment.Depth,
|
||||
parent: Option[UUID]
|
||||
)
|
||||
|
||||
object Comment:
|
||||
|
||||
/**
|
||||
* Represents comment depth. Used for rendition purposes. A depth of 0
|
||||
* represents a top-level comment on some post.
|
||||
*/
|
||||
opaque type Depth = Int
|
||||
|
||||
object Depth:
|
||||
|
||||
/**
|
||||
* Instantiate a new [[Depth]], forcing a minimum value of 0.
|
||||
*
|
||||
* @param value The value, rounded up to 0 if negative.
|
||||
* @return The new [[Depth]].
|
||||
*/
|
||||
def apply(value: Int): Depth =
|
||||
if value < 0 then 0 else value
|
||||
|
||||
given CanEqual[Depth, Depth] = CanEqual.derived
|
||||
|
||||
extension (depth: Depth)
|
||||
/**
|
||||
* @return The integer value of this depth.
|
||||
*/
|
||||
def toInt(): Int = depth
|
||||
|
||||
/**
|
||||
* @return New [[Depth]] that is one level deeper.
|
||||
*/
|
||||
def increment(): Depth = toInt() + 1
|
||||
|
||||
end Depth
|
||||
|
||||
end Comment
|
|
@ -1,25 +0,0 @@
|
|||
package gs.shortform.model
|
||||
|
||||
import gs.uuid.UUID
|
||||
import gs.shortform.crypto.Hash
|
||||
|
||||
/**
|
||||
* Represents a top level piece of content - an article, essay, prompt, or some
|
||||
* other written piece of work. All content must have a [[Title]] for display
|
||||
* purposes.
|
||||
*
|
||||
* Content cannot currently be edited.
|
||||
*
|
||||
* @param externalId Globally unique identifier for this content.
|
||||
* @param createdAt Instant this content was created.
|
||||
* @param createdBy User who created this content.
|
||||
* @param title Display title for this content.
|
||||
* @param hash Hash of the primary rendered file for this content.
|
||||
*/
|
||||
case class Content(
|
||||
externalId: UUID,
|
||||
createdAt: CreatedAt,
|
||||
createdBy: Username,
|
||||
title: Title,
|
||||
hash: Hash
|
||||
)
|
|
@ -0,0 +1,33 @@
|
|||
package gs.shortform.model
|
||||
|
||||
import java.time.Instant
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* Represents the instant some resource expires.
|
||||
*/
|
||||
opaque type ExpiresAt = Instant
|
||||
|
||||
object ExpiresAt:
|
||||
|
||||
def apply(value: Instant): ExpiresAt = value
|
||||
|
||||
def fromOffsetDateTime(value: OffsetDateTime): ExpiresAt = value.toInstant()
|
||||
|
||||
given CanEqual[ExpiresAt, ExpiresAt] = CanEqual.derived
|
||||
|
||||
extension (expiresAt: ExpiresAt)
|
||||
/**
|
||||
* Convert this value to an `Instant`.
|
||||
*/
|
||||
def toInstant(): Instant = expiresAt
|
||||
|
||||
/**
|
||||
* Convert this value to an `OffsetDateTime` with the UTC offset.
|
||||
*/
|
||||
def toOffsetDateTime(): OffsetDateTime =
|
||||
toInstant().atOffset(ZoneOffset.UTC)
|
||||
|
||||
end ExpiresAt
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package gs.shortform.model
|
||||
|
||||
/**
|
||||
* Represents a _password reset token_ that a user can use to set a new
|
||||
* password.
|
||||
*
|
||||
* @param token The unique token.
|
||||
* @param used Whether this token has been used.
|
||||
* @param createdAt The instant this token was created.
|
||||
* @param expiresAt The instant this token expires.
|
||||
*/
|
||||
case class PasswordReset(
|
||||
token: String,
|
||||
used: Boolean,
|
||||
createdAt: CreatedAt,
|
||||
expiresAt: ExpiresAt
|
||||
)
|
25
modules/model/src/main/scala/gs/shortform/model/Post.scala
Normal file
25
modules/model/src/main/scala/gs/shortform/model/Post.scala
Normal file
|
@ -0,0 +1,25 @@
|
|||
package gs.shortform.model
|
||||
|
||||
import gs.uuid.UUID
|
||||
import gs.shortform.crypto.Hash
|
||||
|
||||
/**
|
||||
* Represents a top level piece of content - an article, essay, prompt, or some
|
||||
* other written piece of work. All posts must have a [[Title]] for display
|
||||
* purposes.
|
||||
*
|
||||
* Posts cannot currently be edited.
|
||||
*
|
||||
* @param externalId Globally unique identifier for this post.
|
||||
* @param createdAt Instant this post was created.
|
||||
* @param createdBy User who created this post.
|
||||
* @param title Display title for this post.
|
||||
* @param hash Hash of the primary rendered file for this post.
|
||||
*/
|
||||
case class Post(
|
||||
externalId: UUID,
|
||||
createdAt: CreatedAt,
|
||||
createdBy: Username,
|
||||
title: Title,
|
||||
hash: Hash
|
||||
)
|
Loading…
Add table
Reference in a new issue