Initial commit: Open sourcing all of the Maple Open Technologies code.

This commit is contained in:
Bartlomiej Mika 2025-12-02 14:33:08 -05:00
commit 755d54a99d
2010 changed files with 448675 additions and 0 deletions

View file

@ -0,0 +1,2 @@
DROP INDEX IF EXISTS maplefile.sessions_user_id_idx;
DROP TABLE IF EXISTS maplefile.sessions_by_id;

View file

@ -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
);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.sessions_by_user_id;

View file

@ -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);

View file

@ -0,0 +1,2 @@
DROP INDEX IF EXISTS maplefile.refresh_tokens_user_id_idx;
DROP TABLE IF EXISTS maplefile.refresh_tokens_by_token;

View file

@ -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
);

View file

@ -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;

View file

@ -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
);

View file

@ -0,0 +1 @@
DROP INDEX IF EXISTS maplefile.sessions_user_id_idx;

View file

@ -0,0 +1 @@
CREATE INDEX IF NOT EXISTS sessions_user_id_idx ON maplefile.sessions_by_id (user_id);

View file

@ -0,0 +1 @@
DROP INDEX IF EXISTS maplefile.refresh_tokens_user_id_idx;

View file

@ -0,0 +1 @@
CREATE INDEX IF NOT EXISTS refresh_tokens_user_id_idx ON maplefile.refresh_tokens_by_token (user_id);

View file

@ -0,0 +1 @@
DROP INDEX IF EXISTS maplefile.idx_pkg_cache_expires_at;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.users_by_id;

View file

@ -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
);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.users_by_email;

View file

@ -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
);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.users_by_verification_code;

View file

@ -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
);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.tags_by_id;

View file

@ -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
);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.tags_by_user;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.tag_assignments_by_entity;

View file

@ -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)
);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.collection_members_by_collection_id_and_recipient_id;

View file

@ -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
);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.collections_by_id;

View file

@ -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
);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.collections_by_user_id_with_desc_modified_at_and_asc_collection_id;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.collections_by_user_id_and_access_type_with_desc_modified_at_and_asc_collection_id;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.collections_by_parent_id_with_asc_created_at_and_asc_collection_id;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.collections_by_parent_and_owner_id_with_asc_created_at_and_asc_collection_id;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.collections_by_ancestor_id_with_asc_depth_and_asc_collection_id;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.collections_by_tag_id;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.files_by_id;

View file

@ -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
);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.files_by_collection;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.files_by_owner;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.files_by_creator;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.files_by_tag_id;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.files_by_user;

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS maplefile.storage_daily_usage_by_user_id_with_asc_usage_day;

View file

@ -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);

View file

@ -0,0 +1,3 @@
-- Migration: Drop user_blocked_emails table
DROP TABLE IF EXISTS user_blocked_emails;

View file

@ -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);

View file

@ -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;

View file

@ -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))
);

View 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