Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
|
|
@ -0,0 +1,2 @@
|
|||
DROP INDEX IF EXISTS maplefile.sessions_user_id_idx;
|
||||
DROP TABLE IF EXISTS maplefile.sessions_by_id;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
CREATE TABLE IF NOT EXISTS maplefile.sessions_by_id (
|
||||
session_id UUID PRIMARY KEY,
|
||||
user_id UUID,
|
||||
created_at TIMESTAMP,
|
||||
expires_at TIMESTAMP,
|
||||
last_activity TIMESTAMP,
|
||||
ip_address TEXT,
|
||||
user_agent TEXT,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.sessions_by_user_id;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
CREATE TABLE IF NOT EXISTS maplefile.sessions_by_user_id (
|
||||
user_id UUID,
|
||||
created_at TIMESTAMP,
|
||||
session_id UUID,
|
||||
expires_at TIMESTAMP,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (user_id, created_at, session_id)
|
||||
) WITH CLUSTERING ORDER BY (created_at DESC, session_id ASC);
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
DROP INDEX IF EXISTS maplefile.refresh_tokens_user_id_idx;
|
||||
DROP TABLE IF EXISTS maplefile.refresh_tokens_by_token;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
CREATE TABLE IF NOT EXISTS maplefile.refresh_tokens_by_token (
|
||||
token_hash TEXT PRIMARY KEY,
|
||||
user_id UUID,
|
||||
session_id UUID,
|
||||
created_at TIMESTAMP,
|
||||
expires_at TIMESTAMP,
|
||||
revoked BOOLEAN,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP
|
||||
);
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
DROP INDEX IF EXISTS maplefile.idx_pkg_cache_expires_at;
|
||||
DROP TABLE IF EXISTS maplefile.pkg_cache_by_key_with_asc_expire_at;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE IF NOT EXISTS maplefile.pkg_cache_by_key_with_asc_expire_at (
|
||||
key TEXT PRIMARY KEY,
|
||||
expires_at TIMESTAMP,
|
||||
value BLOB
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP INDEX IF EXISTS maplefile.sessions_user_id_idx;
|
||||
|
|
@ -0,0 +1 @@
|
|||
CREATE INDEX IF NOT EXISTS sessions_user_id_idx ON maplefile.sessions_by_id (user_id);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP INDEX IF EXISTS maplefile.refresh_tokens_user_id_idx;
|
||||
|
|
@ -0,0 +1 @@
|
|||
CREATE INDEX IF NOT EXISTS refresh_tokens_user_id_idx ON maplefile.refresh_tokens_by_token (user_id);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP INDEX IF EXISTS maplefile.idx_pkg_cache_expires_at;
|
||||
|
|
@ -0,0 +1 @@
|
|||
CREATE INDEX IF NOT EXISTS idx_pkg_cache_expires_at ON maplefile.pkg_cache_by_key_with_asc_expire_at (expires_at);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.users_by_id;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
CREATE TABLE IF NOT EXISTS maplefile.users_by_id (
|
||||
id UUID PRIMARY KEY,
|
||||
email TEXT,
|
||||
first_name TEXT,
|
||||
last_name TEXT,
|
||||
name TEXT,
|
||||
lexical_name TEXT,
|
||||
role TINYINT,
|
||||
status TINYINT,
|
||||
timezone TEXT,
|
||||
created_at TIMESTAMP,
|
||||
modified_at TIMESTAMP,
|
||||
profile_data TEXT,
|
||||
security_data TEXT,
|
||||
metadata TEXT,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.users_by_email;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
CREATE TABLE IF NOT EXISTS maplefile.users_by_email (
|
||||
email TEXT PRIMARY KEY,
|
||||
id UUID,
|
||||
first_name TEXT,
|
||||
last_name TEXT,
|
||||
name TEXT,
|
||||
lexical_name TEXT,
|
||||
role TINYINT,
|
||||
status TINYINT,
|
||||
timezone TEXT,
|
||||
created_at TIMESTAMP,
|
||||
modified_at TIMESTAMP,
|
||||
profile_data TEXT,
|
||||
security_data TEXT,
|
||||
metadata TEXT,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.users_by_verification_code;
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
CREATE TABLE IF NOT EXISTS maplefile.users_by_verification_code (
|
||||
verification_code TEXT PRIMARY KEY,
|
||||
id UUID,
|
||||
email TEXT,
|
||||
first_name TEXT,
|
||||
last_name TEXT,
|
||||
name TEXT,
|
||||
lexical_name TEXT,
|
||||
role TINYINT,
|
||||
status TINYINT,
|
||||
timezone TEXT,
|
||||
created_at TIMESTAMP,
|
||||
modified_at TIMESTAMP,
|
||||
profile_data TEXT,
|
||||
security_data TEXT,
|
||||
metadata TEXT
|
||||
,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.tags_by_id;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
-- Main tags table with ALL Tag struct fields
|
||||
-- Tags use E2EE: name and color are encrypted with tag-specific keys
|
||||
CREATE TABLE IF NOT EXISTS maplefile.tags_by_id (
|
||||
-- Identifiers
|
||||
id UUID PRIMARY KEY,
|
||||
user_id UUID,
|
||||
|
||||
-- Encrypted Tag Details (E2EE)
|
||||
encrypted_name TEXT, -- Tag label encrypted with tag key
|
||||
encrypted_color TEXT, -- Hex color encrypted with tag key
|
||||
encrypted_tag_key_ciphertext BLOB, -- Tag key encrypted with user's master key
|
||||
encrypted_tag_key_nonce BLOB, -- Nonce for tag key encryption
|
||||
|
||||
-- Timestamps and versioning
|
||||
created_at TIMESTAMP,
|
||||
modified_at TIMESTAMP,
|
||||
version BIGINT,
|
||||
|
||||
-- State management
|
||||
state TEXT
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.tags_by_user;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
-- Tags indexed by user for efficient listing
|
||||
-- Contains encrypted tag data (E2EE)
|
||||
CREATE TABLE IF NOT EXISTS maplefile.tags_by_user (
|
||||
user_id UUID,
|
||||
id UUID,
|
||||
encrypted_name TEXT, -- Tag label encrypted with tag key
|
||||
encrypted_color TEXT, -- Hex color encrypted with tag key
|
||||
encrypted_tag_key_ciphertext BLOB, -- Tag key encrypted with user's master key
|
||||
encrypted_tag_key_nonce BLOB, -- Nonce for tag key encryption
|
||||
created_at TIMESTAMP,
|
||||
modified_at TIMESTAMP,
|
||||
version BIGINT,
|
||||
state TEXT,
|
||||
PRIMARY KEY (user_id, id)
|
||||
) WITH CLUSTERING ORDER BY (id ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.tag_assignments_by_entity;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
-- Tag assignments indexed by entity (collection or file) for efficient lookup
|
||||
CREATE TABLE IF NOT EXISTS maplefile.tag_assignments_by_entity (
|
||||
entity_id UUID,
|
||||
entity_type TEXT,
|
||||
tag_id UUID,
|
||||
user_id UUID,
|
||||
created_at TIMESTAMP,
|
||||
PRIMARY KEY ((entity_id, entity_type), tag_id)
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.collection_members_by_collection_id_and_recipient_id;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
-- Normalized members table with proper Cassandra naming
|
||||
CREATE TABLE IF NOT EXISTS maplefile.collection_members_by_collection_id_and_recipient_id (
|
||||
collection_id UUID,
|
||||
recipient_id UUID,
|
||||
member_id UUID,
|
||||
recipient_email TEXT,
|
||||
granted_by_id UUID,
|
||||
encrypted_collection_key BLOB,
|
||||
permission_level TEXT,
|
||||
created_at TIMESTAMP,
|
||||
is_inherited BOOLEAN,
|
||||
inherited_from_id UUID,
|
||||
PRIMARY KEY ((collection_id), recipient_id)
|
||||
,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.collections_by_id;
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
-- Main collections table with ALL Collection struct fields
|
||||
CREATE TABLE IF NOT EXISTS maplefile.collections_by_id (
|
||||
-- Identifiers
|
||||
id UUID PRIMARY KEY,
|
||||
owner_id UUID,
|
||||
|
||||
-- Encryption and Content Details
|
||||
encrypted_name TEXT,
|
||||
collection_type TEXT,
|
||||
encrypted_collection_key TEXT,
|
||||
|
||||
-- Custom icon (emoji or predefined icon identifier, encrypted)
|
||||
-- Empty string = default folder icon
|
||||
-- Emoji character (e.g., "📷") = display as emoji
|
||||
-- Icon identifier (e.g., "icon:briefcase") = predefined Heroicon
|
||||
encrypted_custom_icon TEXT,
|
||||
|
||||
-- Hierarchical structure fields
|
||||
parent_id UUID,
|
||||
ancestor_ids TEXT, -- JSON array of UUIDs
|
||||
|
||||
-- File count for performance optimization
|
||||
file_count BIGINT,
|
||||
|
||||
-- Tags assigned to this collection (embedded tag data as JSON)
|
||||
tags TEXT,
|
||||
|
||||
-- Ownership, timestamps and conflict resolution
|
||||
created_at TIMESTAMP,
|
||||
created_by_user_id UUID,
|
||||
modified_at TIMESTAMP,
|
||||
modified_by_user_id UUID,
|
||||
version BIGINT,
|
||||
|
||||
-- State management
|
||||
state TEXT,
|
||||
tombstone_version BIGINT,
|
||||
tombstone_expiry TIMESTAMP,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.collections_by_user_id_with_desc_modified_at_and_asc_collection_id;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
-- User access table (owners + members) with proper Cassandra naming
|
||||
CREATE TABLE IF NOT EXISTS maplefile.collections_by_user_id_with_desc_modified_at_and_asc_collection_id (
|
||||
user_id UUID,
|
||||
modified_at TIMESTAMP,
|
||||
collection_id UUID,
|
||||
access_type TEXT, -- 'owner' or 'member'
|
||||
permission_level TEXT, -- null for owners, actual permission for members
|
||||
state TEXT,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY ((user_id), modified_at, collection_id)
|
||||
) WITH CLUSTERING ORDER BY (modified_at DESC, collection_id ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.collections_by_user_id_and_access_type_with_desc_modified_at_and_asc_collection_id;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
-- For queries like: "Show me only collections I OWN" or "Show me only collections SHARED with me"
|
||||
CREATE TABLE IF NOT EXISTS maplefile.collections_by_user_id_and_access_type_with_desc_modified_at_and_asc_collection_id (
|
||||
user_id UUID,
|
||||
access_type TEXT,
|
||||
modified_at TIMESTAMP,
|
||||
collection_id UUID,
|
||||
permission_level TEXT,
|
||||
state TEXT,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY ((user_id, access_type), modified_at, collection_id)
|
||||
) WITH CLUSTERING ORDER BY (modified_at DESC, collection_id ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.collections_by_parent_id_with_asc_created_at_and_asc_collection_id;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
-- For hierarchical queries: "Show me all direct children of parent X"
|
||||
CREATE TABLE IF NOT EXISTS maplefile.collections_by_parent_id_with_asc_created_at_and_asc_collection_id (
|
||||
parent_id UUID,
|
||||
created_at TIMESTAMP,
|
||||
collection_id UUID,
|
||||
owner_id UUID,
|
||||
state TEXT,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (parent_id, created_at, collection_id)
|
||||
) WITH CLUSTERING ORDER BY (created_at ASC, collection_id ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.collections_by_parent_and_owner_id_with_asc_created_at_and_asc_collection_id;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
-- For user-specific hierarchical queries: "Show me MY direct children of parent X"
|
||||
CREATE TABLE IF NOT EXISTS maplefile.collections_by_parent_and_owner_id_with_asc_created_at_and_asc_collection_id (
|
||||
parent_id UUID,
|
||||
owner_id UUID,
|
||||
created_at TIMESTAMP,
|
||||
collection_id UUID,
|
||||
state TEXT,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY ((parent_id, owner_id), created_at, collection_id)
|
||||
) WITH CLUSTERING ORDER BY (created_at ASC, collection_id ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.collections_by_ancestor_id_with_asc_depth_and_asc_collection_id;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
-- For ALL descendants queries: "Show me ALL nested children (any depth) under collection X"
|
||||
CREATE TABLE IF NOT EXISTS maplefile.collections_by_ancestor_id_with_asc_depth_and_asc_collection_id (
|
||||
ancestor_id UUID,
|
||||
depth INT,
|
||||
collection_id UUID,
|
||||
owner_id UUID,
|
||||
state TEXT,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (ancestor_id, depth, collection_id)
|
||||
) WITH CLUSTERING ORDER BY (depth ASC, collection_id ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.collections_by_tag_id;
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
-- Collections indexed by tag_id for efficient "show all collections with tag X" queries
|
||||
-- This is a denormalized table that duplicates collection data for query performance
|
||||
-- When a collection is updated, ALL entries in this table for that collection must be updated
|
||||
CREATE TABLE IF NOT EXISTS maplefile.collections_by_tag_id (
|
||||
-- Partition key: tag_id allows efficient "get all collections with this tag"
|
||||
tag_id UUID,
|
||||
|
||||
-- Clustering key: collection_id for ordering and uniqueness
|
||||
collection_id UUID,
|
||||
|
||||
-- Denormalized collection data (matches collections_by_id)
|
||||
owner_id UUID,
|
||||
encrypted_name TEXT,
|
||||
collection_type TEXT,
|
||||
encrypted_collection_key TEXT,
|
||||
encrypted_custom_icon TEXT,
|
||||
parent_id UUID,
|
||||
ancestor_ids TEXT,
|
||||
file_count BIGINT,
|
||||
tags TEXT,
|
||||
created_at TIMESTAMP,
|
||||
created_by_user_id UUID,
|
||||
modified_at TIMESTAMP,
|
||||
modified_by_user_id UUID,
|
||||
version BIGINT,
|
||||
state TEXT,
|
||||
tombstone_version BIGINT,
|
||||
tombstone_expiry TIMESTAMP,
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (tag_id, collection_id)
|
||||
) WITH CLUSTERING ORDER BY (collection_id ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.files_by_id;
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
CREATE TABLE IF NOT EXISTS maplefile.files_by_id (
|
||||
-- Identifiers
|
||||
id UUID PRIMARY KEY,
|
||||
collection_id UUID,
|
||||
owner_id UUID,
|
||||
|
||||
-- Encryption and Content Details
|
||||
encrypted_metadata TEXT,
|
||||
encrypted_file_key TEXT, -- JSON serialized
|
||||
encryption_version TEXT,
|
||||
encrypted_hash TEXT,
|
||||
|
||||
-- File Storage Details
|
||||
encrypted_file_object_key TEXT,
|
||||
encrypted_file_size_in_bytes BIGINT,
|
||||
|
||||
-- Thumbnail Storage Details
|
||||
encrypted_thumbnail_object_key TEXT,
|
||||
encrypted_thumbnail_size_in_bytes BIGINT,
|
||||
|
||||
-- Tags assigned to this file (embedded tag data as JSON)
|
||||
tags TEXT,
|
||||
|
||||
-- Timestamps and versioning
|
||||
created_at TIMESTAMP,
|
||||
created_by_user_id UUID,
|
||||
modified_at TIMESTAMP,
|
||||
modified_by_user_id UUID,
|
||||
version BIGINT,
|
||||
|
||||
-- State management
|
||||
state TEXT,
|
||||
tombstone_version BIGINT,
|
||||
tombstone_expiry TIMESTAMP,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.files_by_collection;
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
-- Query files by collection_id, ordered by most recently modified first
|
||||
CREATE TABLE maplefile.files_by_collection (
|
||||
collection_id UUID,
|
||||
modified_at TIMESTAMP,
|
||||
id UUID,
|
||||
owner_id UUID,
|
||||
created_by_user_id UUID,
|
||||
state TEXT,
|
||||
|
||||
-- Encryption and Content Details
|
||||
encrypted_metadata TEXT,
|
||||
encrypted_file_key TEXT,
|
||||
encryption_version TEXT,
|
||||
encrypted_hash TEXT,
|
||||
|
||||
-- File Storage Details
|
||||
encrypted_file_object_key TEXT,
|
||||
encrypted_file_size_in_bytes BIGINT,
|
||||
|
||||
-- Thumbnail Storage Details
|
||||
encrypted_thumbnail_object_key TEXT,
|
||||
encrypted_thumbnail_size_in_bytes BIGINT,
|
||||
|
||||
-- Timestamps and versioning
|
||||
created_at TIMESTAMP,
|
||||
modified_by_user_id UUID,
|
||||
version BIGINT,
|
||||
|
||||
-- State management
|
||||
tombstone_version BIGINT,
|
||||
tombstone_expiry TIMESTAMP,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (collection_id, modified_at, id)
|
||||
) WITH CLUSTERING ORDER BY (modified_at DESC, id ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.files_by_owner;
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
-- Query ALL files owned by a user, ordered by most recently modified first
|
||||
CREATE TABLE maplefile.files_by_owner (
|
||||
owner_id UUID,
|
||||
modified_at TIMESTAMP,
|
||||
id UUID,
|
||||
collection_id UUID,
|
||||
created_by_user_id UUID,
|
||||
state TEXT,
|
||||
|
||||
-- Encryption and Content Details
|
||||
encrypted_metadata TEXT,
|
||||
encrypted_file_key TEXT,
|
||||
encryption_version TEXT,
|
||||
encrypted_hash TEXT,
|
||||
|
||||
-- File Storage Details
|
||||
encrypted_file_object_key TEXT,
|
||||
encrypted_file_size_in_bytes BIGINT,
|
||||
|
||||
-- Thumbnail Storage Details
|
||||
encrypted_thumbnail_object_key TEXT,
|
||||
encrypted_thumbnail_size_in_bytes BIGINT,
|
||||
|
||||
-- Timestamps and versioning
|
||||
created_at TIMESTAMP,
|
||||
modified_by_user_id UUID,
|
||||
version BIGINT,
|
||||
|
||||
-- State management
|
||||
tombstone_version BIGINT,
|
||||
tombstone_expiry TIMESTAMP,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (owner_id, modified_at, id)
|
||||
) WITH CLUSTERING ORDER BY (modified_at DESC, id ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.files_by_creator;
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
-- Query files created by a specific user, ordered by most recently created first
|
||||
CREATE TABLE maplefile.files_by_creator (
|
||||
created_by_user_id UUID,
|
||||
created_at TIMESTAMP,
|
||||
id UUID,
|
||||
owner_id UUID,
|
||||
collection_id UUID,
|
||||
state TEXT,
|
||||
|
||||
-- Encryption and Content Details
|
||||
encrypted_metadata TEXT,
|
||||
encrypted_file_key TEXT,
|
||||
encryption_version TEXT,
|
||||
encrypted_hash TEXT,
|
||||
|
||||
-- File Storage Details
|
||||
encrypted_file_object_key TEXT,
|
||||
encrypted_file_size_in_bytes BIGINT,
|
||||
|
||||
-- Thumbnail Storage Details
|
||||
encrypted_thumbnail_object_key TEXT,
|
||||
encrypted_thumbnail_size_in_bytes BIGINT,
|
||||
|
||||
-- Timestamps and versioning
|
||||
modified_at TIMESTAMP,
|
||||
modified_by_user_id UUID,
|
||||
version BIGINT,
|
||||
|
||||
-- State management
|
||||
tombstone_version BIGINT,
|
||||
tombstone_expiry TIMESTAMP,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (created_by_user_id, created_at, id)
|
||||
) WITH CLUSTERING ORDER BY (created_at DESC, id ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.files_by_tag_id;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
-- Files indexed by tag_id for efficient "show all files with tag X" queries
|
||||
-- This is a denormalized table that duplicates file data for query performance
|
||||
-- When a file is updated, ALL entries in this table for that file must be updated
|
||||
CREATE TABLE IF NOT EXISTS maplefile.files_by_tag_id (
|
||||
-- Partition key: tag_id allows efficient "get all files with this tag"
|
||||
tag_id UUID,
|
||||
|
||||
-- Clustering key: file_id for ordering and uniqueness
|
||||
file_id UUID,
|
||||
|
||||
-- Denormalized file data (matches files_by_id)
|
||||
collection_id UUID,
|
||||
owner_id UUID,
|
||||
encrypted_metadata TEXT,
|
||||
encrypted_file_key TEXT,
|
||||
encryption_version TEXT,
|
||||
encrypted_hash TEXT,
|
||||
encrypted_file_object_key TEXT,
|
||||
encrypted_file_size_in_bytes BIGINT,
|
||||
encrypted_thumbnail_object_key TEXT,
|
||||
encrypted_thumbnail_size_in_bytes BIGINT,
|
||||
tag_ids TEXT,
|
||||
created_at TIMESTAMP,
|
||||
created_by_user_id UUID,
|
||||
modified_at TIMESTAMP,
|
||||
modified_by_user_id UUID,
|
||||
version BIGINT,
|
||||
state TEXT,
|
||||
tombstone_version BIGINT,
|
||||
tombstone_expiry TIMESTAMP,
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (tag_id, file_id)
|
||||
) WITH CLUSTERING ORDER BY (file_id ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.files_by_user;
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
-- Query files by user_id (owner OR member), ordered by most recently modified first
|
||||
CREATE TABLE maplefile.files_by_user (
|
||||
user_id UUID,
|
||||
modified_at TIMESTAMP,
|
||||
id UUID,
|
||||
owner_id UUID,
|
||||
collection_id UUID,
|
||||
created_by_user_id UUID,
|
||||
state TEXT,
|
||||
|
||||
-- Encryption and Content Details
|
||||
encrypted_metadata TEXT,
|
||||
encrypted_file_key TEXT,
|
||||
encryption_version TEXT,
|
||||
encrypted_hash TEXT,
|
||||
|
||||
-- File Storage Details
|
||||
encrypted_file_object_key TEXT,
|
||||
encrypted_file_size_in_bytes BIGINT,
|
||||
|
||||
-- Thumbnail Storage Details
|
||||
encrypted_thumbnail_object_key TEXT,
|
||||
encrypted_thumbnail_size_in_bytes BIGINT,
|
||||
|
||||
-- Embedded tags (full tag data as JSON)
|
||||
tags TEXT,
|
||||
|
||||
-- Timestamps and versioning
|
||||
created_at TIMESTAMP,
|
||||
modified_by_user_id UUID,
|
||||
version BIGINT,
|
||||
|
||||
-- State management
|
||||
tombstone_version BIGINT,
|
||||
tombstone_expiry TIMESTAMP,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (user_id, modified_at, id)
|
||||
) WITH CLUSTERING ORDER BY (modified_at DESC, id ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.storage_usage_events_by_user_id_and_event_day_with_asc_event_time_and_asc_file_id;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
-- Tracks storage usage events for a user on a specific day
|
||||
CREATE TABLE IF NOT EXISTS maplefile.storage_usage_events_by_user_id_and_event_day_with_asc_event_time (
|
||||
user_id UUID,
|
||||
event_day DATE,
|
||||
event_time TIMESTAMP,
|
||||
file_size BIGINT,
|
||||
operation TEXT,
|
||||
event_type TEXT,
|
||||
bytes_delta BIGINT,
|
||||
file_id UUID,
|
||||
collection_id UUID,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY ((user_id, event_day), event_time)
|
||||
) WITH CLUSTERING ORDER BY (event_time ASC);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS maplefile.storage_daily_usage_by_user_id_with_asc_usage_day;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
CREATE TABLE IF NOT EXISTS maplefile.storage_daily_usage_by_user_id_with_asc_usage_day (
|
||||
user_id UUID,
|
||||
usage_day DATE,
|
||||
total_bytes BIGINT,
|
||||
total_add_bytes BIGINT,
|
||||
total_remove_bytes BIGINT,
|
||||
|
||||
-- IP tracking for GDPR compliance
|
||||
created_from_ip_address TEXT,
|
||||
modified_from_ip_address TEXT,
|
||||
ip_anonymized_at TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (user_id, usage_day)
|
||||
) WITH CLUSTERING ORDER BY (usage_day ASC);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
-- Migration: Drop user_blocked_emails table
|
||||
|
||||
DROP TABLE IF EXISTS user_blocked_emails;
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
-- Migration: Create user_blocked_emails table
|
||||
-- Purpose: Store blocked email addresses for each user to prevent unwanted sharing
|
||||
--
|
||||
-- Rationale for this Cassandra Table Structure:
|
||||
-- This table is designed around the primary query: "Fetch all email addresses blocked by a specific user."
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_blocked_emails (
|
||||
-- PARTITION KEY: This is the first component of the primary key. It determines
|
||||
-- data distribution across the cluster. All data for a single user_id will reside
|
||||
-- on the same node (and its replicas), making lookups by user_id very fast.
|
||||
user_id UUID,
|
||||
|
||||
-- CLUSTERING KEY: This determines the on-disk sorting order of rows within a
|
||||
-- partition. For a given user, blocked emails will be stored sorted alphabetically.
|
||||
-- This allows for efficient retrieval of sorted data and enables range queries on the email.
|
||||
blocked_email TEXT,
|
||||
|
||||
-- Data columns associated with the block action.
|
||||
blocked_user_id UUID,
|
||||
reason TEXT,
|
||||
created_at TIMESTAMP,
|
||||
|
||||
-- The PRIMARY KEY defines how data is stored and retrieved.
|
||||
-- The first element (`user_id`) is the Partition Key.
|
||||
-- Subsequent elements (`blocked_email`) are Clustering Keys.
|
||||
-- The combination of all primary key columns uniquely identifies a row, meaning a
|
||||
-- user can block a specific email only once.
|
||||
PRIMARY KEY (user_id, blocked_email)
|
||||
)
|
||||
-- This clause specifies the on-disk sorting order for the clustering key(s).
|
||||
-- In this case, blocked emails within each user's partition will be sorted in
|
||||
-- ascending alphabetical order, which is efficient for display.
|
||||
WITH CLUSTERING ORDER BY (blocked_email ASC);
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
-- Migration: 026_create_invite_email_rate_limits (rollback)
|
||||
-- Description: Drop rate limiting table for invitation emails
|
||||
|
||||
DROP TABLE IF EXISTS invite_email_rate_limits_by_user_id_and_date;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
-- Migration: 026_create_invite_email_rate_limits
|
||||
-- Description: Rate limiting table for invitation emails to non-registered users
|
||||
-- Created: 2024-11-24
|
||||
|
||||
-- Rate limiting for invitation emails
|
||||
-- Uses COUNTER type for atomic increments
|
||||
-- NOTE: Counter tables do not support default_time_to_live in Cassandra.
|
||||
-- TTL must be applied at the UPDATE statement level when incrementing counters.
|
||||
-- Example: UPDATE ... USING TTL 172800 SET emails_sent_today = emails_sent_today + 1 ...
|
||||
CREATE TABLE IF NOT EXISTS invite_email_rate_limits_by_user_id_and_date (
|
||||
user_id UUID,
|
||||
date DATE,
|
||||
emails_sent_today COUNTER,
|
||||
PRIMARY KEY ((user_id, date))
|
||||
);
|
||||
153
cloud/maplefile-backend/migrations/README.md
Normal file
153
cloud/maplefile-backend/migrations/README.md
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
# Database Migrations
|
||||
|
||||
This directory contains Cassandra CQL migrations for the MapleFile backend.
|
||||
|
||||
## ⚠️ Prerequisites: Keyspace Must Exist
|
||||
|
||||
**IMPORTANT:** Before running migrations, the `maplefile` keyspace must exist in Cassandra.
|
||||
|
||||
### Why Keyspaces Are Not in Migrations
|
||||
|
||||
Following industry best practices:
|
||||
- **Keyspace creation = Infrastructure setup** (DevOps responsibility)
|
||||
- **Table migrations = Application changes** (Backend responsibility)
|
||||
|
||||
This separation allows infrastructure decisions (replication strategy, topology) to be managed independently from application schema.
|
||||
|
||||
### Creating the Keyspace
|
||||
|
||||
**Development:**
|
||||
```bash
|
||||
cd cloud/infrastructure/development
|
||||
|
||||
# Find Cassandra container
|
||||
export CASSANDRA_CONTAINER=$(docker ps --filter "name=cassandra" -q | head -1)
|
||||
|
||||
# Create keyspace
|
||||
docker exec -it $CASSANDRA_CONTAINER cqlsh -e "
|
||||
CREATE KEYSPACE IF NOT EXISTS maplefile
|
||||
WITH replication = {
|
||||
'class': 'SimpleStrategy',
|
||||
'replication_factor': 3
|
||||
};"
|
||||
```
|
||||
|
||||
**Production:**
|
||||
See `cloud/infrastructure/production/setup/09_maplefile_backend.md` Step 9.3
|
||||
|
||||
## Auto-Migration
|
||||
|
||||
Migrations run **automatically on backend startup** when `DATABASE_AUTO_MIGRATE=true` (default).
|
||||
|
||||
The backend will:
|
||||
1. **Expect the `maplefile` keyspace to exist** (created by DevOps)
|
||||
2. Run all pending migrations in order (001, 002, 003, etc.)
|
||||
3. Track migration state in Cassandra
|
||||
4. Handle dirty migration states automatically
|
||||
|
||||
## Migration Files
|
||||
|
||||
Migrations use the `golang-migrate` tool format:
|
||||
|
||||
- **Up migrations**: `{version}_{description}.up.cql` - Applied when migrating forward
|
||||
- **Down migrations**: `{version}_{description}.down.cql` - Applied when rolling back
|
||||
|
||||
### Current Migrations
|
||||
|
||||
- **001-024** - Table and index creation for sessions, users, files, collections, etc.
|
||||
|
||||
### Manual Migration
|
||||
|
||||
If you need to run migrations manually:
|
||||
|
||||
```bash
|
||||
# Run all pending migrations
|
||||
./maplefile-backend migrate up
|
||||
|
||||
# Rollback last migration
|
||||
./maplefile-backend migrate down
|
||||
|
||||
# Check current version
|
||||
./maplefile-backend migrate version
|
||||
|
||||
# Force version (fix dirty state)
|
||||
./maplefile-backend migrate force <version>
|
||||
```
|
||||
|
||||
### Disabling Auto-Migration
|
||||
|
||||
Set in `.env`:
|
||||
```bash
|
||||
DATABASE_AUTO_MIGRATE=false
|
||||
```
|
||||
|
||||
## Creating New Migrations
|
||||
|
||||
1. Create new migration files with incremented version:
|
||||
```bash
|
||||
touch migrations/025_add_new_table.up.cql
|
||||
touch migrations/025_add_new_table.down.cql
|
||||
```
|
||||
|
||||
2. Write the CQL:
|
||||
- **Up migration**: Create/modify schema
|
||||
- **Down migration**: Reverse the changes
|
||||
|
||||
3. Commit both files
|
||||
|
||||
4. Restart backend or run `./maplefile-backend migrate up`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "keyspace does not exist" Error
|
||||
|
||||
**Cause**: The `maplefile` keyspace hasn't been created by DevOps/infrastructure setup
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Development
|
||||
cd cloud/infrastructure/development
|
||||
export CASSANDRA_CONTAINER=$(docker ps --filter "name=cassandra" -q | head -1)
|
||||
docker exec -it $CASSANDRA_CONTAINER cqlsh -e "
|
||||
CREATE KEYSPACE IF NOT EXISTS maplefile
|
||||
WITH replication = {
|
||||
'class': 'SimpleStrategy',
|
||||
'replication_factor': 3
|
||||
};"
|
||||
|
||||
# Production
|
||||
# See cloud/infrastructure/production/setup/09_maplefile_backend.md Step 9.3
|
||||
|
||||
# Then restart backend
|
||||
docker service update --force maplefile_backend # Production
|
||||
# Or: task dev:restart # Development
|
||||
```
|
||||
|
||||
**Prevention**: Always create the keyspace before first backend deployment
|
||||
|
||||
### Dirty Migration State
|
||||
|
||||
**Symptom**: Backend won't start, logs show "dirty migration"
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Force clean state at current version
|
||||
./maplefile-backend migrate force <version>
|
||||
|
||||
# Then retry
|
||||
./maplefile-backend migrate up
|
||||
```
|
||||
|
||||
### Migration Failed
|
||||
|
||||
**Symptom**: Backend crashes during migration
|
||||
|
||||
**Solution**:
|
||||
1. Check backend logs for specific CQL error
|
||||
2. Fix the migration file
|
||||
3. Force clean state: `./maplefile-backend migrate force <version-1>`
|
||||
4. Restart backend or run `./maplefile-backend migrate up`
|
||||
|
||||
---
|
||||
|
||||
**Related**: See `pkg/storage/database/cassandradb/migration.go` for implementation
|
||||
Loading…
Add table
Add a link
Reference in a new issue