Added content model entities.

This commit is contained in:
Andrew Lalis 2023-10-19 15:32:08 -04:00
parent 8ace7399ca
commit be880e312f
15 changed files with 391 additions and 1 deletions

3
onyx-api/.gitignore vendored
View File

@ -31,3 +31,6 @@ build/
### VS Code ### ### VS Code ###
.vscode/ .vscode/
*.mv.db
*.db

View File

@ -19,4 +19,4 @@ Therefore, you can think of onyx as a more robust, secure digital drive for your
## Authentication ## Authentication
Because each onyx node operates independently, there is no central authentication service, but instead each node maintains a set of users, and issues short-lived tokens granting users access to secure resources. When a user needs to access resources from a networked onyx node, their home node will send the user's id and the home node's own thing. Because each onyx node operates independently, there is no central authentication service, but instead each node maintains a set of users, and issues short-lived tokens granting users access to secure resources. When a user needs to access resources from a networked onyx node, they'll use their own token, which the networked node will independently verify was issued recently by the user's home node.

View File

@ -0,0 +1,26 @@
package com.andrewlalis.onyx.content;
/**
* The different levels of access that can be set for content in an onyx node.
*/
public enum ContentAccessLevel {
/**
* An access level that does not permit reading or editing the content.
*/
NONE,
/**
* An access level that permits reading content, but not editing.
*/
VIEW,
/**
* An access level that permits reading and editing content.
*/
EDIT,
/**
* An access level that takes the parent node's access level.
*/
INHERIT
}

View File

@ -0,0 +1,37 @@
package com.andrewlalis.onyx.content;
import jakarta.persistence.*;
import lombok.Getter;
/**
* An entity meant to be attached to a {@link ContentNode}, which contains the
* access levels for the different ways in which a content node can be accessed.
*/
@Entity
@Table(name = "content_access_rules")
@Getter
public class ContentAccessRules {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
/**
* The content node that these access rules apply to.
*/
@OneToOne(mappedBy = "accessInfo", fetch = FetchType.LAZY)
private ContentNode contentNode;
/** The access level that applies to users from outside this onyx network. */
@Enumerated(EnumType.ORDINAL) @Column(nullable = false, columnDefinition = "TINYINT NOT NULL")
private ContentAccessLevel publicAccessLevel = ContentAccessLevel.INHERIT;
/** The access level that applies to users from within this onyx network. */
@Enumerated(EnumType.ORDINAL) @Column(nullable = false, columnDefinition = "TINYINT NOT NULL")
private ContentAccessLevel networkAccessLevel = ContentAccessLevel.INHERIT;
/** The access level that applies to users within only this onyx node. */
@Enumerated(EnumType.ORDINAL) @Column(nullable = false, columnDefinition = "TINYINT NOT NULL")
private ContentAccessLevel nodeAccessLevel = ContentAccessLevel.INHERIT;
// TODO: Add a user allowlist.
}

View File

@ -0,0 +1,19 @@
package com.andrewlalis.onyx.content;
import jakarta.persistence.*;
import java.util.Set;
/**
* A type of content node that contains a list of children, which could
* themselves be any type of content node.
*/
@Entity
@Table(name = "content_node_container")
public final class ContentContainerNode extends ContentNode {
/**
* The set of children that belong to this container.
*/
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "parentContainer")
private Set<ContentNode> children;
}

View File

@ -0,0 +1,22 @@
package com.andrewlalis.onyx.content;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Lob;
import jakarta.persistence.Table;
/**
* A type of content node that contains a single document.
*/
@Entity
@Table(name = "content_node_document")
public class ContentDocumentNode extends ContentNode {
@Column(nullable = false, updatable = false, length = 127)
private String type;
/**
* The raw file content for this document.
*/
@Lob @Column(nullable = false)
private byte[] content;
}

View File

@ -0,0 +1,56 @@
package com.andrewlalis.onyx.content;
import com.andrewlalis.onyx.content.history.ContentNodeHistory;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/**
* The abstract model that represents all nodes in the system's hierarchical
* content tree, including both <em>containers</em> and <em>documents</em>.
*/
@Entity
@Table(name = "content_node")
@Inheritance(strategy = InheritanceType.JOINED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class ContentNode {
public static final int MAX_NAME_LENGTH = 127;
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(nullable = false, length = MAX_NAME_LENGTH)
private String name;
@Enumerated(EnumType.ORDINAL) @Column(columnDefinition = "TINYINT NOT NULL")
private Type type;
@OneToOne(fetch = FetchType.LAZY, optional = false, orphanRemoval = true, cascade = CascadeType.ALL)
private ContentAccessRules accessInfo;
/**
* The container node that this one belongs to. This will be null <strong>only</strong>
* in the case of the root node, which is a special, hidden container node
* that acts as the top-level of the content hierarchy.
*/
@ManyToOne(fetch = FetchType.LAZY)
private ContentContainerNode parentContainer;
@OneToOne(fetch = FetchType.LAZY, optional = false, orphanRemoval = true, cascade = CascadeType.ALL)
private ContentNodeHistory history;
@Column(nullable = false)
private boolean archived;
public enum Type {
CONTAINER,
DOCUMENT
}
public ContentNode(String name, Type type, ContentContainerNode parentContainer) {
this.name = name;
this.type = type;
this.parentContainer = parentContainer;
this.history = new ContentNodeHistory(this);
}
}

View File

@ -0,0 +1,37 @@
package com.andrewlalis.onyx.content.history;
import com.andrewlalis.onyx.content.ContentNode;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
/**
* An entity that represents the complete history of a single content node,
* storing an ordered list of entries detailing how that node has changed.
*/
@Entity
@Table(name = "content_node_history")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ContentNodeHistory {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@OneToOne(mappedBy = "history")
private ContentNode contentNode;
@CreationTimestamp
private LocalDateTime createdAt;
@OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "history")
private Set<ContentNodeHistoryEntry> entries;
public ContentNodeHistory(ContentNode contentNode) {
this.contentNode = contentNode;
this.entries = new HashSet<>();
}
}

View File

@ -0,0 +1,35 @@
package com.andrewlalis.onyx.content.history;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
/**
* The base class for all types of content node history entries. It defines
* some basic properties that all entries should have.
*/
@Entity
@Table(name = "content_node_history_entry")
@Inheritance(strategy = InheritanceType.JOINED)
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class ContentNodeHistoryEntry {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@CreationTimestamp
private LocalDateTime timestamp;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private ContentNodeHistory history;
// TODO: Add user info (or system)
public ContentNodeHistoryEntry(ContentNodeHistory history) {
this.history = history;
}
}

View File

@ -0,0 +1,53 @@
package com.andrewlalis.onyx.content.history.entry;
import com.andrewlalis.onyx.content.ContentAccessLevel;
import com.andrewlalis.onyx.content.history.ContentNodeHistory;
import com.andrewlalis.onyx.content.history.ContentNodeHistoryEntry;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* A history entry that records a modification to a content node's access rules
* by a user or the system.
*/
@Entity
@Table(name = "content_node_history_entry_access_rules")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class AccessRulesEntry extends ContentNodeHistoryEntry {
@Enumerated(EnumType.ORDINAL) @Column(nullable = false, columnDefinition = "TINYINT NOT NULL")
private ContentAccessLevel oldPublicAccessLevel;
@Enumerated(EnumType.ORDINAL) @Column(nullable = false, columnDefinition = "TINYINT NOT NULL")
private ContentAccessLevel oldNetworkAccessLevel;
@Enumerated(EnumType.ORDINAL) @Column(nullable = false, columnDefinition = "TINYINT NOT NULL")
private ContentAccessLevel oldNodeAccessLevel;
@Enumerated(EnumType.ORDINAL) @Column(nullable = false, columnDefinition = "TINYINT NOT NULL")
private ContentAccessLevel newPublicAccessLevel;
@Enumerated(EnumType.ORDINAL) @Column(nullable = false, columnDefinition = "TINYINT NOT NULL")
private ContentAccessLevel newNetworkAccessLevel;
@Enumerated(EnumType.ORDINAL) @Column(nullable = false, columnDefinition = "TINYINT NOT NULL")
private ContentAccessLevel newNodeAccessLevel;
// TODO: add usersAllowed, usersDisallowed
public AccessRulesEntry(
ContentNodeHistory history,
ContentAccessLevel oldPublicAccessLevel,
ContentAccessLevel oldNetworkAccessLevel,
ContentAccessLevel oldNodeAccessLevel,
ContentAccessLevel newPublicAccessLevel,
ContentAccessLevel newNetworkAccessLevel,
ContentAccessLevel newNodeAccessLevel
) {
super(history);
this.oldPublicAccessLevel = oldPublicAccessLevel;
this.oldNetworkAccessLevel = oldNetworkAccessLevel;
this.oldNodeAccessLevel = oldNodeAccessLevel;
this.newPublicAccessLevel = newPublicAccessLevel;
this.newNetworkAccessLevel = newNetworkAccessLevel;
this.newNodeAccessLevel = newNodeAccessLevel;
}
}

View File

@ -0,0 +1,27 @@
package com.andrewlalis.onyx.content.history.entry;
import com.andrewlalis.onyx.content.history.ContentNodeHistory;
import com.andrewlalis.onyx.content.history.ContentNodeHistoryEntry;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* History entry for tracking the state of a content node's `archived` status.
*/
@Entity
@Table(name = "content_node_history_entry_archived")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ArchivedEntry extends ContentNodeHistoryEntry {
@Column(nullable = false, updatable = false)
private boolean archived;
public ArchivedEntry(ContentNodeHistory history, boolean archived) {
super(history);
this.archived = archived;
}
}

View File

@ -0,0 +1,35 @@
package com.andrewlalis.onyx.content.history.entry;
import com.andrewlalis.onyx.content.ContentNode;
import com.andrewlalis.onyx.content.history.ContentNodeHistory;
import com.andrewlalis.onyx.content.history.ContentNodeHistoryEntry;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/**
* History entry for tracking updates to the contents of a container node.
*/
@Entity
@Table(name = "content_node_history_entry_container_edit")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ContainerEditEntry extends ContentNodeHistoryEntry {
public enum EditType {
CONTAINER_ADDED,
CONTAINER_REMOVED,
DOCUMENT_ADDED,
DOCUMENT_REMOVED
}
@Enumerated(EnumType.ORDINAL)
private EditType type;
@Column(nullable = false, updatable = false, length = ContentNode.MAX_NAME_LENGTH)
private String affectedNodeName;
public ContainerEditEntry(ContentNodeHistory history, EditType type, String affectedNodeName) {
super(history);
this.type = type;
this.affectedNodeName = affectedNodeName;
}
}

View File

@ -0,0 +1,29 @@
package com.andrewlalis.onyx.content.history.entry;
import com.andrewlalis.onyx.content.ContentNode;
import com.andrewlalis.onyx.content.history.ContentNodeHistory;
import com.andrewlalis.onyx.content.history.ContentNodeHistoryEntry;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "content_node_history_entry_rename")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RenameEntry extends ContentNodeHistoryEntry {
@Column(nullable = false, updatable = false, length = ContentNode.MAX_NAME_LENGTH)
private String oldName;
@Column(nullable = false, updatable = false, length = ContentNode.MAX_NAME_LENGTH)
private String newName;
public RenameEntry(ContentNodeHistory history, String oldName, String newName) {
super(history);
this.oldName = oldName;
this.newName = newName;
}
}

View File

@ -0,0 +1,6 @@
/**
* This package defines the <strong>content</strong> module for the onyx node,
* which is responsible for managing the actual hierarchical set of documents
* that are directly stored by the node.
*/
package com.andrewlalis.onyx.content;

View File

@ -0,0 +1,5 @@
# Development-specific settings.
spring.datasource.url=jdbc:h2:./onyx-db-dev
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create