V3 version with backward comp
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
-- VaultDrop 012 down: drop V3 new tables
|
||||
DROP FUNCTION IF EXISTS resolve_effective_role;
|
||||
DROP TABLE IF EXISTS rebac_relations;
|
||||
DROP TABLE IF EXISTS sync_queue;
|
||||
DROP TABLE IF EXISTS retention_policies;
|
||||
DROP TABLE IF EXISTS resource_placements;
|
||||
DROP TABLE IF EXISTS storage_locations;
|
||||
DROP TABLE IF EXISTS resource_variants;
|
||||
DROP TABLE IF EXISTS resources;
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS parent_user_id;
|
||||
@@ -0,0 +1,145 @@
|
||||
-- VaultDrop 012: V3 new tables - resources, storage_locations, resource_placements, etc.
|
||||
|
||||
-- Add parent_user_id to users for groups/organizations
|
||||
ALTER TABLE users ADD COLUMN parent_user_id UUID REFERENCES users(id);
|
||||
|
||||
-- Resources (replaces files)
|
||||
CREATE TABLE resources (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
mime_type TEXT NOT NULL DEFAULT '',
|
||||
size BIGINT NOT NULL DEFAULT 0,
|
||||
checksum TEXT NOT NULL DEFAULT '',
|
||||
ocr_text TEXT NOT NULL DEFAULT '',
|
||||
is_folder BOOLEAN NOT NULL DEFAULT false,
|
||||
parent_resource_id UUID REFERENCES resources(id),
|
||||
owner_id UUID NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_resources_owner ON resources(owner_id);
|
||||
CREATE INDEX idx_resources_parent ON resources(parent_resource_id);
|
||||
CREATE INDEX idx_resources_checksum_owner ON resources(checksum, owner_id);
|
||||
|
||||
-- Resource variants (replaces thumbnails)
|
||||
CREATE TABLE resource_variants (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
resource_id UUID NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
|
||||
variant_type TEXT NOT NULL,
|
||||
page_number INTEGER NOT NULL DEFAULT 1,
|
||||
width INTEGER NOT NULL DEFAULT 0,
|
||||
height INTEGER NOT NULL DEFAULT 0,
|
||||
mime_type TEXT NOT NULL DEFAULT 'image/jpeg',
|
||||
generated_by TEXT NOT NULL DEFAULT 'server' CHECK (generated_by IN ('server', 'client')),
|
||||
storage_key TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_variants_resource ON resource_variants(resource_id);
|
||||
CREATE UNIQUE INDEX idx_variants_resource_type_page ON resource_variants(resource_id, variant_type, page_number);
|
||||
|
||||
-- Storage locations (devices, server, backup)
|
||||
CREATE TABLE storage_locations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id),
|
||||
device_name TEXT NOT NULL,
|
||||
role TEXT NOT NULL CHECK (role IN ('primary', 'device', 'backup', 'server')),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_seen_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_locations_user ON storage_locations(user_id);
|
||||
|
||||
-- Resource placements (pivot resource × storage_location)
|
||||
CREATE TABLE resource_placements (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
resource_id UUID NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
|
||||
storage_location_id UUID NOT NULL REFERENCES storage_locations(id),
|
||||
status TEXT NOT NULL DEFAULT 'synced' CHECK (status IN ('local_only', 'synced', 'cloud_only', 'pending_upload', 'pending_download')),
|
||||
storage_key TEXT,
|
||||
synced_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(resource_id, storage_location_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_placements_resource ON resource_placements(resource_id);
|
||||
CREATE INDEX idx_placements_location ON resource_placements(storage_location_id);
|
||||
CREATE INDEX idx_placements_status ON resource_placements(status);
|
||||
|
||||
-- Retention policies
|
||||
CREATE TABLE retention_policies (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id),
|
||||
storage_location_id UUID NOT NULL REFERENCES storage_locations(id),
|
||||
rule_type TEXT NOT NULL,
|
||||
rule_value JSONB NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_policies_user ON retention_policies(user_id);
|
||||
CREATE INDEX idx_policies_location ON retention_policies(storage_location_id);
|
||||
|
||||
-- Sync queue
|
||||
CREATE TABLE sync_queue (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
resource_id UUID NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
|
||||
storage_location_id UUID NOT NULL REFERENCES storage_locations(id),
|
||||
operation TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
attempts INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_queue_status ON sync_queue(status);
|
||||
CREATE INDEX idx_queue_resource ON sync_queue(resource_id);
|
||||
CREATE INDEX idx_queue_location ON sync_queue(storage_location_id);
|
||||
|
||||
-- ReBAC relations
|
||||
CREATE TABLE rebac_relations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
resource_id UUID NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
|
||||
subject_user_id UUID NOT NULL REFERENCES users(id),
|
||||
role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'editor', 'viewer')),
|
||||
granted_by UUID NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(resource_id, subject_user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_rebac_resource ON rebac_relations(resource_id);
|
||||
CREATE INDEX idx_rebac_subject ON rebac_relations(subject_user_id);
|
||||
|
||||
-- Function to resolve effective role with recursive parent/group traversal
|
||||
CREATE OR REPLACE FUNCTION resolve_effective_role(p_user_id UUID, p_resource_id UUID)
|
||||
RETURNS TEXT AS $$
|
||||
DECLARE
|
||||
v_role TEXT;
|
||||
BEGIN
|
||||
WITH RECURSIVE rtree AS (
|
||||
SELECT r.id, r.parent_resource_id, r.owner_id
|
||||
FROM resources r
|
||||
WHERE r.id = p_resource_id
|
||||
UNION ALL
|
||||
SELECT r.id, r.parent_resource_id, r.owner_id
|
||||
FROM resources r
|
||||
JOIN rtree ON r.id = rtree.parent_resource_id
|
||||
)
|
||||
SELECT CASE
|
||||
WHEN EXISTS(SELECT 1 FROM rtree WHERE owner_id = p_user_id) THEN 'owner'
|
||||
ELSE COALESCE(
|
||||
(SELECT rr.role::text FROM rebac_relations rr
|
||||
JOIN rtree ON rr.resource_id = rtree.id
|
||||
WHERE rr.subject_user_id = p_user_id
|
||||
ORDER BY CASE rr.role
|
||||
WHEN 'owner' THEN 0
|
||||
WHEN 'admin' THEN 1
|
||||
WHEN 'editor' THEN 2
|
||||
WHEN 'viewer' THEN 3
|
||||
END ASC LIMIT 1),
|
||||
''
|
||||
)
|
||||
END INTO v_role;
|
||||
RETURN v_role;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -0,0 +1,15 @@
|
||||
-- VaultDrop 013 down: revert data migration
|
||||
-- Re-add tag_type to tags
|
||||
ALTER TABLE tags ADD COLUMN IF NOT EXISTS tag_type TEXT NOT NULL DEFAULT 'none';
|
||||
|
||||
DROP TABLE IF EXISTS resource_tags;
|
||||
|
||||
DELETE FROM resource_variants;
|
||||
|
||||
DELETE FROM rebac_relations;
|
||||
|
||||
DELETE FROM resource_placements;
|
||||
|
||||
DELETE FROM storage_locations;
|
||||
|
||||
DELETE FROM resources;
|
||||
@@ -0,0 +1,100 @@
|
||||
-- VaultDrop 013: Migrate data from V1 to V3
|
||||
-- Assigns all existing files to user 'pixel'
|
||||
|
||||
-- 1. Ensure user 'pixel' exists (create if not)
|
||||
INSERT INTO users (username, password_hash)
|
||||
SELECT 'pixel', '$argon2id$v=19$m=65536,t=1,p=4$placeholder$placeholder'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM users WHERE username = 'pixel');
|
||||
|
||||
-- 2. Migrate files -> resources
|
||||
INSERT INTO resources (id, name, mime_type, size, checksum, ocr_text, is_folder, parent_resource_id, owner_id, created_at, updated_at)
|
||||
SELECT
|
||||
f.id::uuid,
|
||||
f.name,
|
||||
f.mime_type,
|
||||
f.size,
|
||||
f.checksum,
|
||||
f.ocr_text,
|
||||
f.is_folder,
|
||||
f.parent_file_id::uuid,
|
||||
(SELECT id FROM users WHERE username = 'pixel'),
|
||||
f.created_at,
|
||||
f.updated_at
|
||||
FROM files f
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- 3. Create server storage_location for user 'pixel'
|
||||
INSERT INTO storage_locations (id, user_id, device_name, role)
|
||||
SELECT
|
||||
gen_random_uuid(),
|
||||
(SELECT id FROM users WHERE username = 'pixel'),
|
||||
'VaultDrop Server',
|
||||
'server'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM storage_locations
|
||||
WHERE user_id = (SELECT id FROM users WHERE username = 'pixel')
|
||||
AND role = 'server'
|
||||
);
|
||||
|
||||
-- 4. Create resource_placements for migrated resources
|
||||
INSERT INTO resource_placements (resource_id, storage_location_id, status, storage_key, synced_at)
|
||||
SELECT
|
||||
r.id,
|
||||
sl.id,
|
||||
'synced',
|
||||
f.storage_key,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM resources r
|
||||
JOIN files f ON f.id::uuid = r.id
|
||||
CROSS JOIN storage_locations sl
|
||||
WHERE sl.user_id = (SELECT id FROM users WHERE username = 'pixel')
|
||||
AND sl.role = 'server'
|
||||
ON CONFLICT (resource_id, storage_location_id) DO NOTHING;
|
||||
|
||||
-- 5. Create rebac_relations (owner) for all migrated resources
|
||||
INSERT INTO rebac_relations (resource_id, subject_user_id, role, granted_by)
|
||||
SELECT
|
||||
r.id,
|
||||
u.id,
|
||||
'owner',
|
||||
u.id
|
||||
FROM resources r
|
||||
CROSS JOIN (SELECT id FROM users WHERE username = 'pixel') u
|
||||
ON CONFLICT (resource_id, subject_user_id) DO NOTHING;
|
||||
|
||||
-- 6. Migrate thumbnails -> resource_variants
|
||||
INSERT INTO resource_variants (id, resource_id, variant_type, page_number, width, height, mime_type, generated_by, storage_key, created_at)
|
||||
SELECT
|
||||
t.id::uuid,
|
||||
t.file_id::uuid,
|
||||
CASE
|
||||
WHEN t.resolution_label = 'thumbnail' THEN 'thumbnail_small'
|
||||
WHEN t.resolution_label = 'full' THEN 'thumbnail_full'
|
||||
ELSE t.resolution_label
|
||||
END,
|
||||
t.page_number,
|
||||
t.width,
|
||||
t.height,
|
||||
t.mime_type,
|
||||
'server',
|
||||
t.storage_key,
|
||||
t.created_at
|
||||
FROM thumbnails t
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- 7. Create resource_tags from file_tags (keep old file_tags for now)
|
||||
CREATE TABLE IF NOT EXISTS resource_tags (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tag_id TEXT,
|
||||
resource_id TEXT
|
||||
);
|
||||
|
||||
INSERT INTO resource_tags (id, tag_id, resource_id)
|
||||
SELECT
|
||||
gen_random_uuid(),
|
||||
ft.tag_id,
|
||||
ft.file_id
|
||||
FROM file_tags ft;
|
||||
|
||||
-- 8. Remove tag_type column from tags
|
||||
ALTER TABLE tags DROP COLUMN IF EXISTS tag_type;
|
||||
@@ -0,0 +1,33 @@
|
||||
-- VaultDrop 014 down: restore legacy tables
|
||||
|
||||
CREATE TABLE files (
|
||||
id TEXT PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
mime_type TEXT NOT NULL DEFAULT '',
|
||||
size BIGINT NOT NULL DEFAULT 0,
|
||||
storage_key TEXT NOT NULL DEFAULT '',
|
||||
checksum TEXT NOT NULL DEFAULT '',
|
||||
ocr_text TEXT NOT NULL DEFAULT '',
|
||||
is_folder BOOLEAN NOT NULL DEFAULT false,
|
||||
parent_file_id TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE thumbnails (
|
||||
id TEXT PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
file_id TEXT NOT NULL REFERENCES files(id) ON DELETE CASCADE,
|
||||
page_number INTEGER NOT NULL,
|
||||
resolution_label TEXT NOT NULL,
|
||||
width INTEGER NOT NULL,
|
||||
height INTEGER NOT NULL,
|
||||
storage_key TEXT NOT NULL,
|
||||
mime_type TEXT NOT NULL DEFAULT 'image/jpeg',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE file_tags (
|
||||
id TEXT PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
|
||||
tag_id TEXT,
|
||||
file_id TEXT
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
-- VaultDrop 014: Drop legacy V1 tables
|
||||
|
||||
-- Tags now use resource_tags instead of file_tags
|
||||
DROP TABLE IF EXISTS file_tags;
|
||||
|
||||
-- Thumbnails migrated to resource_variants
|
||||
DROP TABLE IF EXISTS thumbnails;
|
||||
|
||||
-- Files migrated to resources
|
||||
DROP TABLE IF EXISTS files;
|
||||
Reference in New Issue
Block a user