share folders and files
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS share_links;
|
||||
DROP TABLE IF EXISTS shares;
|
||||
@@ -0,0 +1,34 @@
|
||||
-- Partages user→user (docs/api-v1.md §6.1) : une seule ligne active par
|
||||
-- (resource, grantee) — soft-revoked_at ; le déclin de l'outbox propage
|
||||
-- état, pas l'inverse.
|
||||
CREATE TABLE shares (
|
||||
id TEXT PRIMARY KEY CHECK (id ~ '^[0-9a-f]{32}$'),
|
||||
resource_id TEXT NOT NULL REFERENCES resources(resource_id) ON DELETE CASCADE,
|
||||
grantee_user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
access TEXT NOT NULL CHECK (access IN ('viewer','commenter','editor')),
|
||||
inherit BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_by TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
expires_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
revoked_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_shares_resource ON shares(resource_id);
|
||||
CREATE INDEX idx_shares_grantee ON shares(grantee_user_id) WHERE revoked_at IS NULL;
|
||||
-- Une seule ligne active par paire (resource, grantee)
|
||||
CREATE UNIQUE INDEX idx_shares_active ON shares(resource_id, grantee_user_id) WHERE revoked_at IS NULL;
|
||||
|
||||
-- Liens de partage publics : token = id (généré par le client, 32-hex).
|
||||
CREATE TABLE share_links (
|
||||
id TEXT PRIMARY KEY CHECK (id ~ '^[0-9a-f]{32}$'),
|
||||
resource_id TEXT NOT NULL REFERENCES resources(resource_id) ON DELETE CASCADE,
|
||||
access TEXT NOT NULL CHECK (access IN ('viewer','commenter','editor')),
|
||||
created_by TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
expires_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
revoked_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_share_links_token ON share_links(id) WHERE revoked_at IS NULL;
|
||||
CREATE INDEX idx_share_links_resource ON share_links(resource_id);
|
||||
@@ -221,6 +221,55 @@ func TestMigrationsUpDown(t *testing.T) {
|
||||
t.Error("contrainte UNIQUE(device_id, operation_id) manquante sur operations")
|
||||
}
|
||||
|
||||
// --- 000009 : shares + share_links ------------------------------------
|
||||
assertTables(t, conn, "users", "devices", "resources", "operations", "ocr_jobs", "shares", "share_links", "schema_migrations")
|
||||
|
||||
assertHexCheck(t, conn, "shares", "id")
|
||||
assertHexCheck(t, conn, "share_links", "id")
|
||||
|
||||
// shares access CHECK
|
||||
var sharesAccessCheck int
|
||||
err = conn.QueryRow(`
|
||||
SELECT COUNT(*) FROM pg_constraint c
|
||||
JOIN pg_class t ON t.oid = c.conrelid
|
||||
WHERE t.relname = 'shares'
|
||||
AND pg_get_constraintdef(c.oid) LIKE '%viewer%'
|
||||
AND pg_get_constraintdef(c.oid) LIKE '%editor%'`).Scan(&sharesAccessCheck)
|
||||
if err != nil {
|
||||
t.Fatalf("check shares access: %v", err)
|
||||
}
|
||||
if sharesAccessCheck == 0 {
|
||||
t.Error("CHECK (access IN ('viewer','commenter','editor')) manquant sur shares")
|
||||
}
|
||||
|
||||
// shares unique partiel (resource_id, grantee_user_id) WHERE revoked_at IS NULL
|
||||
var sharesActiveIdx int
|
||||
err = conn.QueryRow(`
|
||||
SELECT COUNT(*) FROM pg_index i
|
||||
JOIN pg_class t ON t.oid = i.indrelid
|
||||
WHERE t.relname = 'shares' AND i.indisunique
|
||||
AND i.indpred IS NOT NULL`).Scan(&sharesActiveIdx)
|
||||
if err != nil {
|
||||
t.Fatalf("shares active index: %v", err)
|
||||
}
|
||||
if sharesActiveIdx == 0 {
|
||||
t.Error("index unique partiel (resource_id, grantee_user_id) WHERE revoked_at IS NULL manquant sur shares")
|
||||
}
|
||||
|
||||
// share_links token unique partiel
|
||||
var linksTokenIdx int
|
||||
err = conn.QueryRow(`
|
||||
SELECT COUNT(*) FROM pg_index i
|
||||
JOIN pg_class t ON t.oid = i.indrelid
|
||||
WHERE t.relname = 'share_links' AND i.indisunique
|
||||
AND i.indpred IS NOT NULL`).Scan(&linksTokenIdx)
|
||||
if err != nil {
|
||||
t.Fatalf("share_links token index: %v", err)
|
||||
}
|
||||
if linksTokenIdx == 0 {
|
||||
t.Error("index unique partiel (id) WHERE revoked_at IS NULL manquant sur share_links")
|
||||
}
|
||||
|
||||
if err := MigrateDownDatabase(url); err != nil {
|
||||
t.Fatalf("migrate down: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user