From f036cf136d84ed6b1b5ef24f5f18f68e2153ab1b Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:42:10 +0100 Subject: [PATCH 01/11] feat(db): org default video visibility and password columns --- .../migrations/0040_crazy_champions.sql | 1 + .../migrations/meta/0040_snapshot.json | 4103 +++++++++++++++++ .../database/migrations/meta/_journal.json | 7 + packages/database/schema.ts | 2 + 4 files changed, 4113 insertions(+) create mode 100644 packages/database/migrations/0040_crazy_champions.sql create mode 100644 packages/database/migrations/meta/0040_snapshot.json diff --git a/packages/database/migrations/0040_crazy_champions.sql b/packages/database/migrations/0040_crazy_champions.sql new file mode 100644 index 0000000000..81fff8186d --- /dev/null +++ b/packages/database/migrations/0040_crazy_champions.sql @@ -0,0 +1 @@ +ALTER TABLE `organizations` ADD `defaultVideoPassword` text; \ No newline at end of file diff --git a/packages/database/migrations/meta/0040_snapshot.json b/packages/database/migrations/meta/0040_snapshot.json new file mode 100644 index 0000000000..1b18cd2085 --- /dev/null +++ b/packages/database/migrations/meta/0040_snapshot.json @@ -0,0 +1,4103 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "7cc4302a-23e9-470c-9b41-aaa121a66b45", + "prevId": "c95c8755-4ef8-4c51-ad10-5bd19717d269", + "tables": { + "accounts": { + "name": "accounts", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_in": { + "name": "expires_in", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_in": { + "name": "refresh_token_expires_in", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + }, + "tempColumn": { + "name": "tempColumn", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_id_idx": { + "name": "user_id_idx", + "columns": ["userId"], + "isUnique": false + }, + "provider_account_id_idx": { + "name": "provider_account_id_idx", + "columns": ["providerAccountId"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "accounts_id": { + "name": "accounts_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "agent_api_authorization_codes": { + "name": "agent_api_authorization_codes", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "codeHash": { + "name": "codeHash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "codeChallenge": { + "name": "codeChallenge", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "redirectUri": { + "name": "redirectUri", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "json", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "consumedAt": { + "name": "consumedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": { + "code_hash_idx": { + "name": "code_hash_idx", + "columns": ["codeHash"], + "isUnique": true + }, + "expires_at_idx": { + "name": "expires_at_idx", + "columns": ["expiresAt"], + "isUnique": false + }, + "user_created_at_idx": { + "name": "user_created_at_idx", + "columns": ["userId", "createdAt"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "agent_api_authorization_codes_id": { + "name": "agent_api_authorization_codes_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "agent_api_idempotency": { + "name": "agent_api_idempotency", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "operation": { + "name": "operation", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "keyHash": { + "name": "keyHash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "requestHash": { + "name": "requestHash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "statusCode": { + "name": "statusCode", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "response": { + "name": "response", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "user_operation_key_idx": { + "name": "user_operation_key_idx", + "columns": ["userId", "operation", "keyHash"], + "isUnique": true + }, + "expires_at_idx": { + "name": "expires_at_idx", + "columns": ["expiresAt"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "agent_api_idempotency_id": { + "name": "agent_api_idempotency_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "agent_api_keys": { + "name": "agent_api_keys", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tokenHash": { + "name": "tokenHash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Cap CLI'" + }, + "scopes": { + "name": "scopes", + "type": "json", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revokedAt": { + "name": "revokedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "token_hash_idx": { + "name": "token_hash_idx", + "columns": ["tokenHash"], + "isUnique": true + }, + "user_created_at_idx": { + "name": "user_created_at_idx", + "columns": ["userId", "createdAt"], + "isUnique": false + }, + "expires_at_idx": { + "name": "expires_at_idx", + "columns": ["expiresAt"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "agent_api_keys_id": { + "name": "agent_api_keys_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "agent_api_operations": { + "name": "agent_api_operations", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resourceId": { + "name": "resourceId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resultResourceId": { + "name": "resultResourceId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'queued'" + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "errorCode": { + "name": "errorCode", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "errorMessage": { + "name": "errorMessage", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_created_at_idx": { + "name": "user_created_at_idx", + "columns": ["userId", "createdAt"], + "isUnique": false + }, + "state_updated_at_idx": { + "name": "state_updated_at_idx", + "columns": ["state", "updatedAt"], + "isUnique": false + }, + "resource_id_idx": { + "name": "resource_id_idx", + "columns": ["resourceId"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "agent_api_operations_id": { + "name": "agent_api_operations_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "auth_api_keys": { + "name": "auth_api_keys", + "columns": { + "id": { + "name": "id", + "type": "varchar(36)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": { + "user_id_created_at_idx": { + "name": "user_id_created_at_idx", + "columns": ["userId", "createdAt"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "auth_api_keys_id": { + "name": "auth_api_keys_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "comments": { + "name": "comments", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "float", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorId": { + "name": "authorId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "videoId": { + "name": "videoId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + }, + "parentCommentId": { + "name": "parentCommentId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mediaKey": { + "name": "mediaKey", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mediaDuration": { + "name": "mediaDuration", + "type": "float", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mediaMeta": { + "name": "mediaMeta", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "video_type_created_idx": { + "name": "video_type_created_idx", + "columns": ["videoId", "type", "createdAt", "id"], + "isUnique": false + }, + "author_id_idx": { + "name": "author_id_idx", + "columns": ["authorId"], + "isUnique": false + }, + "parent_comment_id_idx": { + "name": "parent_comment_id_idx", + "columns": ["parentCommentId"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "comments_id": { + "name": "comments_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "developer_api_keys": { + "name": "developer_api_keys", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "appId": { + "name": "appId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "keyType": { + "name": "keyType", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "keyPrefix": { + "name": "keyPrefix", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "keyHash": { + "name": "keyHash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encryptedKey": { + "name": "encryptedKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revokedAt": { + "name": "revokedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": { + "key_hash_idx": { + "name": "key_hash_idx", + "columns": ["keyHash"], + "isUnique": true + }, + "app_key_type_idx": { + "name": "app_key_type_idx", + "columns": ["appId", "keyType"], + "isUnique": false + } + }, + "foreignKeys": { + "developer_api_keys_appId_developer_apps_id_fk": { + "name": "developer_api_keys_appId_developer_apps_id_fk", + "tableFrom": "developer_api_keys", + "tableTo": "developer_apps", + "columnsFrom": ["appId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "developer_api_keys_id": { + "name": "developer_api_keys_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "developer_app_domains": { + "name": "developer_app_domains", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "appId": { + "name": "appId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "varchar(253)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": { + "developer_app_domains_appId_developer_apps_id_fk": { + "name": "developer_app_domains_appId_developer_apps_id_fk", + "tableFrom": "developer_app_domains", + "tableTo": "developer_apps", + "columnsFrom": ["appId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "developer_app_domains_id": { + "name": "developer_app_domains_id", + "columns": ["id"] + } + }, + "uniqueConstraints": { + "app_domain_unique": { + "name": "app_domain_unique", + "columns": ["appId", "domain"] + } + }, + "checkConstraint": {} + }, + "developer_apps": { + "name": "developer_apps", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ownerId": { + "name": "ownerId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "environment": { + "name": "environment", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logoUrl": { + "name": "logoUrl", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "owner_deleted_idx": { + "name": "owner_deleted_idx", + "columns": ["ownerId", "deletedAt"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "developer_apps_id": { + "name": "developer_apps_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "developer_credit_accounts": { + "name": "developer_credit_accounts", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "appId": { + "name": "appId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ownerId": { + "name": "ownerId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "balanceMicroCredits": { + "name": "balanceMicroCredits", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stripePaymentMethodId": { + "name": "stripePaymentMethodId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "autoTopUpEnabled": { + "name": "autoTopUpEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "autoTopUpThresholdMicroCredits": { + "name": "autoTopUpThresholdMicroCredits", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "autoTopUpAmountCents": { + "name": "autoTopUpAmountCents", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "app_id_unique": { + "name": "app_id_unique", + "columns": ["appId"], + "isUnique": true + } + }, + "foreignKeys": { + "developer_credit_accounts_appId_developer_apps_id_fk": { + "name": "developer_credit_accounts_appId_developer_apps_id_fk", + "tableFrom": "developer_credit_accounts", + "tableTo": "developer_apps", + "columnsFrom": ["appId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "developer_credit_accounts_id": { + "name": "developer_credit_accounts_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "developer_credit_transactions": { + "name": "developer_credit_transactions", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "accountId": { + "name": "accountId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amountMicroCredits": { + "name": "amountMicroCredits", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "balanceAfterMicroCredits": { + "name": "balanceAfterMicroCredits", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "referenceId": { + "name": "referenceId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referenceType": { + "name": "referenceType", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": { + "account_type_created_idx": { + "name": "account_type_created_idx", + "columns": ["accountId", "type", "createdAt"], + "isUnique": false + }, + "account_ref_dedup_idx": { + "name": "account_ref_dedup_idx", + "columns": ["accountId", "referenceId", "referenceType"], + "isUnique": false + } + }, + "foreignKeys": { + "dev_credit_txn_account_fk": { + "name": "dev_credit_txn_account_fk", + "tableFrom": "developer_credit_transactions", + "tableTo": "developer_credit_accounts", + "columnsFrom": ["accountId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "developer_credit_transactions_id": { + "name": "developer_credit_transactions_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "developer_daily_storage_snapshots": { + "name": "developer_daily_storage_snapshots", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "appId": { + "name": "appId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshotDate": { + "name": "snapshotDate", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "totalDurationMinutes": { + "name": "totalDurationMinutes", + "type": "float", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "videoCount": { + "name": "videoCount", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "microCreditsCharged": { + "name": "microCreditsCharged", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "processedAt": { + "name": "processedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": { + "developer_daily_storage_snapshots_appId_developer_apps_id_fk": { + "name": "developer_daily_storage_snapshots_appId_developer_apps_id_fk", + "tableFrom": "developer_daily_storage_snapshots", + "tableTo": "developer_apps", + "columnsFrom": ["appId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "developer_daily_storage_snapshots_id": { + "name": "developer_daily_storage_snapshots_id", + "columns": ["id"] + } + }, + "uniqueConstraints": { + "app_date_unique": { + "name": "app_date_unique", + "columns": ["appId", "snapshotDate"] + } + }, + "checkConstraint": {} + }, + "developer_videos": { + "name": "developer_videos", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "appId": { + "name": "appId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "externalUserId": { + "name": "externalUserId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Untitled'" + }, + "duration": { + "name": "duration", + "type": "float", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fps": { + "name": "fps", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "s3Key": { + "name": "s3Key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "transcriptionStatus": { + "name": "transcriptionStatus", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "app_created_idx": { + "name": "app_created_idx", + "columns": ["appId", "createdAt"], + "isUnique": false + }, + "app_user_idx": { + "name": "app_user_idx", + "columns": ["appId", "externalUserId"], + "isUnique": false + }, + "app_deleted_idx": { + "name": "app_deleted_idx", + "columns": ["appId", "deletedAt"], + "isUnique": false + } + }, + "foreignKeys": { + "developer_videos_appId_developer_apps_id_fk": { + "name": "developer_videos_appId_developer_apps_id_fk", + "tableFrom": "developer_videos", + "tableTo": "developer_apps", + "columnsFrom": ["appId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "developer_videos_id": { + "name": "developer_videos_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "folders": { + "name": "folders", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'normal'" + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "settings": { + "name": "settings", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organizationId": { + "name": "organizationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdById": { + "name": "createdById", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parentId": { + "name": "parentId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "spaceId": { + "name": "spaceId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "organization_id_idx": { + "name": "organization_id_idx", + "columns": ["organizationId"], + "isUnique": false + }, + "created_by_id_idx": { + "name": "created_by_id_idx", + "columns": ["createdById"], + "isUnique": false + }, + "parent_id_idx": { + "name": "parent_id_idx", + "columns": ["parentId"], + "isUnique": false + }, + "space_id_idx": { + "name": "space_id_idx", + "columns": ["spaceId"], + "isUnique": false + }, + "public_parent_id_idx": { + "name": "public_parent_id_idx", + "columns": ["public", "parentId"], + "isUnique": false + }, + "public_space_parent_id_idx": { + "name": "public_space_parent_id_idx", + "columns": ["public", "spaceId", "parentId"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "folders_id": { + "name": "folders_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "imported_videos": { + "name": "imported_videos", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "orgId": { + "name": "orgId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_id": { + "name": "source_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "imported_videos_orgId_source_source_id_pk": { + "name": "imported_videos_orgId_source_source_id_pk", + "columns": ["orgId", "source", "source_id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "integration_installations": { + "name": "integration_installations", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "externalId": { + "name": "externalId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "displayName": { + "name": "displayName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organizationId": { + "name": "organizationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "installedByUserId": { + "name": "installedByUserId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encryptedCredentials": { + "name": "encryptedCredentials", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "provider_external_id_idx": { + "name": "provider_external_id_idx", + "columns": ["provider", "externalId"], + "isUnique": true + }, + "organization_provider_display_name_idx": { + "name": "organization_provider_display_name_idx", + "columns": ["organizationId", "provider", "displayName"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "integration_installations_id": { + "name": "integration_installations_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "messenger_conversations": { + "name": "messenger_conversations", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agent": { + "name": "agent", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mode": { + "name": "mode", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'agent'" + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "anonymousId": { + "name": "anonymousId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "takeoverByUserId": { + "name": "takeoverByUserId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "takeoverAt": { + "name": "takeoverAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + }, + "lastMessageAt": { + "name": "lastMessageAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": { + "user_last_message_idx": { + "name": "user_last_message_idx", + "columns": ["userId", "lastMessageAt"], + "isUnique": false + }, + "anonymous_last_message_idx": { + "name": "anonymous_last_message_idx", + "columns": ["anonymousId", "lastMessageAt"], + "isUnique": false + }, + "mode_last_message_idx": { + "name": "mode_last_message_idx", + "columns": ["mode", "lastMessageAt"], + "isUnique": false + }, + "updated_at_idx": { + "name": "updated_at_idx", + "columns": ["updatedAt"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "messenger_conversations_id": { + "name": "messenger_conversations_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "messenger_messages": { + "name": "messenger_messages", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "conversationId": { + "name": "conversationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "anonymousId": { + "name": "anonymousId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": { + "conversation_created_at_idx": { + "name": "conversation_created_at_idx", + "columns": ["conversationId", "createdAt"], + "isUnique": false + }, + "role_created_at_idx": { + "name": "role_created_at_idx", + "columns": ["role", "createdAt"], + "isUnique": false + } + }, + "foreignKeys": { + "messenger_messages_conversationId_messenger_conversations_id_fk": { + "name": "messenger_messages_conversationId_messenger_conversations_id_fk", + "tableFrom": "messenger_messages", + "tableTo": "messenger_conversations", + "columnsFrom": ["conversationId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "messenger_messages_id": { + "name": "messenger_messages_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "messenger_support_emails": { + "name": "messenger_support_emails", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "conversationId": { + "name": "conversationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userEmail": { + "name": "userEmail", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": { + "support_email_user_created_at_idx": { + "name": "support_email_user_created_at_idx", + "columns": ["userId", "createdAt"], + "isUnique": false + }, + "support_email_conversation_created_at_idx": { + "name": "support_email_conversation_created_at_idx", + "columns": ["conversationId", "createdAt"], + "isUnique": false + } + }, + "foreignKeys": { + "support_email_conversation_fk": { + "name": "support_email_conversation_fk", + "tableFrom": "messenger_support_emails", + "tableTo": "messenger_conversations", + "columnsFrom": ["conversationId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "messenger_support_emails_id": { + "name": "messenger_support_emails_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "notifications": { + "name": "notifications", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "orgId": { + "name": "orgId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipientId": { + "name": "recipientId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "videoId": { + "name": "videoId", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dedupKey": { + "name": "dedupKey", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "readAt": { + "name": "readAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": { + "org_id_idx": { + "name": "org_id_idx", + "columns": ["orgId"], + "isUnique": false + }, + "type_idx": { + "name": "type_idx", + "columns": ["type"], + "isUnique": false + }, + "read_at_idx": { + "name": "read_at_idx", + "columns": ["readAt"], + "isUnique": false + }, + "created_at_idx": { + "name": "created_at_idx", + "columns": ["createdAt"], + "isUnique": false + }, + "recipient_read_idx": { + "name": "recipient_read_idx", + "columns": ["recipientId", "readAt"], + "isUnique": false + }, + "recipient_created_idx": { + "name": "recipient_created_idx", + "columns": ["recipientId", "createdAt"], + "isUnique": false + }, + "dedup_key_idx": { + "name": "dedup_key_idx", + "columns": ["dedupKey"], + "isUnique": true + }, + "type_recipient_created_idx": { + "name": "type_recipient_created_idx", + "columns": ["type", "recipientId", "createdAt"], + "isUnique": false + }, + "type_recipient_video_created_idx": { + "name": "type_recipient_video_created_idx", + "columns": ["type", "recipientId", "videoId", "createdAt"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "notifications_id": { + "name": "notifications_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "organization_invites": { + "name": "organization_invites", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organizationId": { + "name": "organizationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "invitedEmail": { + "name": "invitedEmail", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "invitedByUserId": { + "name": "invitedByUserId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "organization_id_idx": { + "name": "organization_id_idx", + "columns": ["organizationId"], + "isUnique": false + }, + "invited_email_idx": { + "name": "invited_email_idx", + "columns": ["invitedEmail"], + "isUnique": false + }, + "invited_by_user_id_idx": { + "name": "invited_by_user_id_idx", + "columns": ["invitedByUserId"], + "isUnique": false + }, + "status_idx": { + "name": "status_idx", + "columns": ["status"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "organization_invites_id": { + "name": "organization_invites_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organizationId": { + "name": "organizationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hasProSeat": { + "name": "hasProSeat", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "organization_id_idx": { + "name": "organization_id_idx", + "columns": ["organizationId"], + "isUnique": false + }, + "user_id_organization_id_idx": { + "name": "user_id_organization_id_idx", + "columns": ["userId", "organizationId"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "organization_members_id": { + "name": "organization_members_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ownerId": { + "name": "ownerId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tombstoneAt": { + "name": "tombstoneAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allowedEmailDomain": { + "name": "allowedEmailDomain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customDomain": { + "name": "customDomain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domainVerified": { + "name": "domainVerified", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "defaultVideoPassword": { + "name": "defaultVideoPassword", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "iconUrl": { + "name": "iconUrl", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shareableLinkIconUrl": { + "name": "shareableLinkIconUrl", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + }, + "workosOrganizationId": { + "name": "workosOrganizationId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workosConnectionId": { + "name": "workosConnectionId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "owner_id_tombstone_idx": { + "name": "owner_id_tombstone_idx", + "columns": ["ownerId", "tombstoneAt"], + "isUnique": false + }, + "custom_domain_idx": { + "name": "custom_domain_idx", + "columns": ["customDomain"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "organizations_id": { + "name": "organizations_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "s3_buckets": { + "name": "s3_buckets", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ownerId": { + "name": "ownerId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organizationId": { + "name": "organizationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bucketName": { + "name": "bucketName", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "accessKeyId": { + "name": "accessKeyId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "secretAccessKey": { + "name": "secretAccessKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('aws')" + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "owner_organization_idx": { + "name": "owner_organization_idx", + "columns": ["ownerId", "organizationId"], + "isUnique": false + }, + "organization_id_idx": { + "name": "organization_id_idx", + "columns": ["organizationId"], + "isUnique": false + }, + "organization_active_idx": { + "name": "organization_active_idx", + "columns": ["organizationId", "active", "updatedAt"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "s3_buckets_id": { + "name": "s3_buckets_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sessionToken": { + "name": "sessionToken", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "datetime", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "session_token_idx": { + "name": "session_token_idx", + "columns": ["sessionToken"], + "isUnique": true + }, + "user_id_idx": { + "name": "user_id_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "sessions_id": { + "name": "sessions_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_videos": { + "name": "shared_videos", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "videoId": { + "name": "videoId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folderId": { + "name": "folderId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organizationId": { + "name": "organizationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sharedByUserId": { + "name": "sharedByUserId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sharedAt": { + "name": "sharedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": { + "folder_id_idx": { + "name": "folder_id_idx", + "columns": ["folderId"], + "isUnique": false + }, + "organization_id_idx": { + "name": "organization_id_idx", + "columns": ["organizationId"], + "isUnique": false + }, + "shared_by_user_id_idx": { + "name": "shared_by_user_id_idx", + "columns": ["sharedByUserId"], + "isUnique": false + }, + "video_id_organization_id_idx": { + "name": "video_id_organization_id_idx", + "columns": ["videoId", "organizationId"], + "isUnique": false + }, + "video_id_folder_id_idx": { + "name": "video_id_folder_id_idx", + "columns": ["videoId", "folderId"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "shared_videos_id": { + "name": "shared_videos_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "signed_baas": { + "name": "signed_baas", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organizationId": { + "name": "organizationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entityName": { + "name": "entityName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entityType": { + "name": "entityType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entityAddress": { + "name": "entityAddress", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signerName": { + "name": "signerName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signerTitle": { + "name": "signerTitle", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "noticesEmail": { + "name": "noticesEmail", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signatureData": { + "name": "signatureData", + "type": "longtext", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signedAt": { + "name": "signedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailSentAt": { + "name": "emailSentAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "signed_baa_organization_id_idx": { + "name": "signed_baa_organization_id_idx", + "columns": ["organizationId"], + "isUnique": true + }, + "signed_baa_stripe_subscription_idx": { + "name": "signed_baa_stripe_subscription_idx", + "columns": ["stripeSubscriptionId"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "signed_baas_id": { + "name": "signed_baas_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "space_members": { + "name": "space_members", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "spaceId": { + "name": "spaceId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "user_id_idx": { + "name": "user_id_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "space_members_id": { + "name": "space_members_id", + "columns": ["id"] + } + }, + "uniqueConstraints": { + "space_id_user_id_unique": { + "name": "space_id_user_id_unique", + "columns": ["spaceId", "userId"] + } + }, + "checkConstraint": {} + }, + "space_videos": { + "name": "space_videos", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "spaceId": { + "name": "spaceId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folderId": { + "name": "folderId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "videoId": { + "name": "videoId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "addedById": { + "name": "addedById", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "addedAt": { + "name": "addedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": { + "folder_id_idx": { + "name": "folder_id_idx", + "columns": ["folderId"], + "isUnique": false + }, + "video_id_idx": { + "name": "video_id_idx", + "columns": ["videoId"], + "isUnique": false + }, + "added_by_id_idx": { + "name": "added_by_id_idx", + "columns": ["addedById"], + "isUnique": false + }, + "space_id_video_id_idx": { + "name": "space_id_video_id_idx", + "columns": ["spaceId", "videoId"], + "isUnique": false + }, + "space_id_folder_id_idx": { + "name": "space_id_folder_id_idx", + "columns": ["spaceId", "folderId"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "space_videos_id": { + "name": "space_videos_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "spaces": { + "name": "spaces", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "primary": { + "name": "primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organizationId": { + "name": "organizationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdById": { + "name": "createdById", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "iconUrl": { + "name": "iconUrl", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + }, + "privacy": { + "name": "privacy", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Private'" + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "organization_id_idx": { + "name": "organization_id_idx", + "columns": ["organizationId"], + "isUnique": false + }, + "created_by_id_idx": { + "name": "created_by_id_idx", + "columns": ["createdById"], + "isUnique": false + }, + "public_organization_id_idx": { + "name": "public_organization_id_idx", + "columns": ["public", "organizationId"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "spaces_id": { + "name": "spaces_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "storage_integrations": { + "name": "storage_integrations", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ownerId": { + "name": "ownerId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organizationId": { + "name": "organizationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "displayName": { + "name": "displayName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "encryptedConfig": { + "name": "encryptedConfig", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "googleDriveAccessToken": { + "name": "googleDriveAccessToken", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "googleDriveAccessTokenExpiresAt": { + "name": "googleDriveAccessTokenExpiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "googleDriveTokenRefreshLeaseId": { + "name": "googleDriveTokenRefreshLeaseId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "googleDriveTokenRefreshLeaseExpiresAt": { + "name": "googleDriveTokenRefreshLeaseExpiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "googleDriveStorageQuotaCache": { + "name": "googleDriveStorageQuotaCache", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "owner_provider_idx": { + "name": "owner_provider_idx", + "columns": ["ownerId", "provider"], + "isUnique": false + }, + "owner_active_idx": { + "name": "owner_active_idx", + "columns": ["ownerId", "active"], + "isUnique": false + }, + "organization_provider_idx": { + "name": "organization_provider_idx", + "columns": ["organizationId", "provider"], + "isUnique": false + }, + "organization_active_idx": { + "name": "organization_active_idx", + "columns": ["organizationId", "active", "status"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "storage_integrations_id": { + "name": "storage_integrations_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "storage_objects": { + "name": "storage_objects", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integrationId": { + "name": "integrationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ownerId": { + "name": "ownerId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "videoId": { + "name": "videoId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "objectKey": { + "name": "objectKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "objectKeyHash": { + "name": "objectKeyHash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerObjectId": { + "name": "providerObjectId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploadSessionUrl": { + "name": "uploadSessionUrl", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uploadStatus": { + "name": "uploadStatus", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "contentType": { + "name": "contentType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contentLength": { + "name": "contentLength", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "integration_key_hash_idx": { + "name": "integration_key_hash_idx", + "columns": ["integrationId", "objectKeyHash"], + "isUnique": true + }, + "integration_status_idx": { + "name": "integration_status_idx", + "columns": ["integrationId", "uploadStatus"], + "isUnique": false + }, + "video_id_idx": { + "name": "video_id_idx", + "columns": ["videoId"], + "isUnique": false + }, + "owner_id_idx": { + "name": "owner_id_idx", + "columns": ["ownerId"], + "isUnique": false + } + }, + "foreignKeys": { + "storage_objects_integrationId_storage_integrations_id_fk": { + "name": "storage_objects_integrationId_storage_integrations_id_fk", + "tableFrom": "storage_objects", + "tableTo": "storage_integrations", + "columnsFrom": ["integrationId"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "storage_objects_id": { + "name": "storage_objects_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lastName": { + "name": "lastName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "thirdPartyStripeSubscriptionId": { + "name": "thirdPartyStripeSubscriptionId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stripeSubscriptionStatus": { + "name": "stripeSubscriptionStatus", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stripeSubscriptionPriceId": { + "name": "stripeSubscriptionPriceId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "preferences": { + "name": "preferences", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('null')" + }, + "activeOrganizationId": { + "name": "activeOrganizationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + }, + "onboardingSteps": { + "name": "onboardingSteps", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "onboarding_completed_at": { + "name": "onboarding_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customBucket": { + "name": "customBucket", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inviteQuota": { + "name": "inviteQuota", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "defaultOrgId": { + "name": "defaultOrgId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authSessionVersion": { + "name": "authSessionVersion", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "email_idx": { + "name": "email_idx", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "users_id": { + "name": "users_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "verification_tokens": { + "name": "verification_tokens", + "columns": { + "identifier": { + "name": "identifier", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "datetime", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verification_tokens_identifier": { + "name": "verification_tokens_identifier", + "columns": ["identifier"] + } + }, + "uniqueConstraints": { + "verification_tokens_token_unique": { + "name": "verification_tokens_token_unique", + "columns": ["token"] + } + }, + "checkConstraint": {} + }, + "video_edits": { + "name": "video_edits", + "columns": { + "videoId": { + "name": "videoId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sourceKey": { + "name": "sourceKey", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "editSpec": { + "name": "editSpec", + "type": "json", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": { + "video_edits_videoId_videos_id_fk": { + "name": "video_edits_videoId_videos_id_fk", + "tableFrom": "video_edits", + "tableTo": "videos", + "columnsFrom": ["videoId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "video_edits_videoId": { + "name": "video_edits_videoId", + "columns": ["videoId"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "video_uploads": { + "name": "video_uploads", + "columns": { + "video_id": { + "name": "video_id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded": { + "name": "uploaded", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "total": { + "name": "total", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "mode": { + "name": "mode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phase": { + "name": "phase", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'uploading'" + }, + "processing_progress": { + "name": "processing_progress", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "processing_message": { + "name": "processing_message", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "raw_file_key": { + "name": "raw_file_key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "phase_updated_at_video_id_idx": { + "name": "phase_updated_at_video_id_idx", + "columns": ["phase", "updated_at", "video_id"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "video_uploads_video_id": { + "name": "video_uploads_video_id", + "columns": ["video_id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "videos": { + "name": "videos", + "columns": { + "id": { + "name": "id", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ownerId": { + "name": "ownerId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "orgId": { + "name": "orgId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'My Video'" + }, + "bucket": { + "name": "bucket", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "storageIntegrationId": { + "name": "storageIntegrationId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration": { + "name": "duration", + "type": "float", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fps": { + "name": "fps", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "settings": { + "name": "settings", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "transcriptionStatus": { + "name": "transcriptionStatus", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "json", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{\"type\":\"MediaConvert\"}')" + }, + "folderId": { + "name": "folderId", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "effectiveCreatedAt": { + "name": "effectiveCreatedAt", + "type": "datetime", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "generated": { + "as": "COALESCE(\n STR_TO_DATE(JSON_UNQUOTE(JSON_EXTRACT(`metadata`, '$.customCreatedAt')), '%Y-%m-%dT%H:%i:%s.%fZ'),\n STR_TO_DATE(JSON_UNQUOTE(JSON_EXTRACT(`metadata`, '$.customCreatedAt')), '%Y-%m-%dT%H:%i:%sZ'),\n `createdAt`\n )", + "type": "stored" + } + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "xStreamInfo": { + "name": "xStreamInfo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "firstViewEmailSentAt": { + "name": "firstViewEmailSentAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "isScreenshot": { + "name": "isScreenshot", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "awsRegion": { + "name": "awsRegion", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "awsBucket": { + "name": "awsBucket", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "videoStartTime": { + "name": "videoStartTime", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "audioStartTime": { + "name": "audioStartTime", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jobId": { + "name": "jobId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jobStatus": { + "name": "jobStatus", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "skipProcessing": { + "name": "skipProcessing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "owner_id_idx": { + "name": "owner_id_idx", + "columns": ["ownerId"], + "isUnique": false + }, + "is_public_idx": { + "name": "is_public_idx", + "columns": ["public"], + "isUnique": false + }, + "folder_id_idx": { + "name": "folder_id_idx", + "columns": ["folderId"], + "isUnique": false + }, + "storage_integration_id_idx": { + "name": "storage_integration_id_idx", + "columns": ["storageIntegrationId"], + "isUnique": false + }, + "org_owner_folder_idx": { + "name": "org_owner_folder_idx", + "columns": ["orgId", "ownerId", "folderId"], + "isUnique": false + }, + "org_effective_created_idx": { + "name": "org_effective_created_idx", + "columns": ["orgId", "effectiveCreatedAt"], + "isUnique": false + } + }, + "foreignKeys": { + "videos_storageIntegrationId_storage_integrations_id_fk": { + "name": "videos_storageIntegrationId_storage_integrations_id_fk", + "tableFrom": "videos", + "tableTo": "storage_integrations", + "columnsFrom": ["storageIntegrationId"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "videos_id": { + "name": "videos_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} diff --git a/packages/database/migrations/meta/_journal.json b/packages/database/migrations/meta/_journal.json index d5ab96235d..def4eaa6f7 100644 --- a/packages/database/migrations/meta/_journal.json +++ b/packages/database/migrations/meta/_journal.json @@ -281,6 +281,13 @@ "when": 1787096888361, "tag": "0039_magical_vermin", "breakpoints": true + }, + { + "idx": 40, + "version": "5", + "when": 1787217975605, + "tag": "0040_crazy_champions", + "breakpoints": true } ] } diff --git a/packages/database/schema.ts b/packages/database/schema.ts index 1510b6d44e..1793ebcb61 100644 --- a/packages/database/schema.ts +++ b/packages/database/schema.ts @@ -211,7 +211,9 @@ export const organizations = mysqlTable( shareableLinkUseOrganizationIcon?: boolean; aiGenerationLanguage?: AiGenerationLanguage; defaultPlaybackSpeed?: number; + defaultVideoPublic?: boolean; }>(), + defaultVideoPassword: encryptedTextNullable("defaultVideoPassword"), iconUrl: varchar("iconUrl", { length: 1024, }).$type(), From 763a60f3024cb3b57d9cb6e9e987436fe50015fb Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:42:15 +0100 Subject: [PATCH 02/11] feat(web-backend): add resolveNewVideoDefaults resolver --- .../__tests__/unit/new-video-defaults.test.ts | 64 +++++++++++++++++++ .../src/Videos/NewVideoDefaults.ts | 30 +++++++++ packages/web-backend/src/index.ts | 4 ++ 3 files changed, 98 insertions(+) create mode 100644 apps/web/__tests__/unit/new-video-defaults.test.ts create mode 100644 packages/web-backend/src/Videos/NewVideoDefaults.ts diff --git a/apps/web/__tests__/unit/new-video-defaults.test.ts b/apps/web/__tests__/unit/new-video-defaults.test.ts new file mode 100644 index 0000000000..ebc4ab8a36 --- /dev/null +++ b/apps/web/__tests__/unit/new-video-defaults.test.ts @@ -0,0 +1,64 @@ +import { type DbClient, resolveNewVideoDefaults } from "@cap/web-backend"; +import type { Organisation } from "@cap/web-domain"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const env = vi.hoisted(() => ({ defaultPublic: true })); + +vi.mock("@cap/env", async (importOriginal) => ({ + ...(await importOriginal()), + serverEnv: () => ({ CAP_VIDEOS_DEFAULT_PUBLIC: env.defaultPublic }), +})); + +type OrganizationRow = { + settings: { defaultVideoPublic?: boolean } | null; + defaultVideoPassword: string | null; +}; + +const dbReturning = (rows: OrganizationRow[]) => + ({ + select: () => ({ from: () => ({ where: async () => rows }) }), + }) as unknown as DbClient; + +const orgId = "org-1" as Organisation.OrganisationId; + +describe("resolveNewVideoDefaults", () => { + beforeEach(() => { + env.defaultPublic = true; + }); + + it("falls back to the env default when the organization row is missing", async () => { + expect(await resolveNewVideoDefaults(dbReturning([]), orgId)).toEqual({ + public: true, + password: null, + }); + + env.defaultPublic = false; + + expect(await resolveNewVideoDefaults(dbReturning([]), orgId)).toEqual({ + public: false, + password: null, + }); + }); + + it("prefers the organization setting over the env default", async () => { + const db = dbReturning([ + { settings: { defaultVideoPublic: false }, defaultVideoPassword: null }, + ]); + + expect(await resolveNewVideoDefaults(db, orgId)).toEqual({ + public: false, + password: null, + }); + }); + + it("passes the stored password hash through", async () => { + const db = dbReturning([ + { settings: null, defaultVideoPassword: "hashed-password" }, + ]); + + expect(await resolveNewVideoDefaults(db, orgId)).toEqual({ + public: true, + password: "hashed-password", + }); + }); +}); diff --git a/packages/web-backend/src/Videos/NewVideoDefaults.ts b/packages/web-backend/src/Videos/NewVideoDefaults.ts new file mode 100644 index 0000000000..acef078e84 --- /dev/null +++ b/packages/web-backend/src/Videos/NewVideoDefaults.ts @@ -0,0 +1,30 @@ +import * as Db from "@cap/database/schema"; +import { serverEnv } from "@cap/env"; +import type { Organisation } from "@cap/web-domain"; +import * as Dz from "drizzle-orm"; +import type { DbClient } from "../Database.ts"; + +export type NewVideoDefaults = { + public: boolean; + password: string | null; +}; + +export async function resolveNewVideoDefaults( + db: DbClient, + orgId: Organisation.OrganisationId, +): Promise { + const [organization] = await db + .select({ + settings: Db.organizations.settings, + defaultVideoPassword: Db.organizations.defaultVideoPassword, + }) + .from(Db.organizations) + .where(Dz.eq(Db.organizations.id, orgId)); + + return { + public: + organization?.settings?.defaultVideoPublic ?? + serverEnv().CAP_VIDEOS_DEFAULT_PUBLIC, + password: organization?.defaultVideoPassword ?? null, + }; +} diff --git a/packages/web-backend/src/index.ts b/packages/web-backend/src/index.ts index 4ded8a58b3..8c76fed0b0 100644 --- a/packages/web-backend/src/index.ts +++ b/packages/web-backend/src/index.ts @@ -39,6 +39,10 @@ export { type ViewerSettings, } from "./Videos/EffectiveVideoRules.ts"; export { findScreenshotObjectKey, Videos } from "./Videos/index.ts"; +export { + type NewVideoDefaults, + resolveNewVideoDefaults, +} from "./Videos/NewVideoDefaults.ts"; export { buildCanView, VideosPolicy, From 23d1fe1edc620f488304c892c282fa19ccd8f71d Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:42:15 +0100 Subject: [PATCH 03/11] feat: apply org default sharing at all video creation paths --- .../unit/desktop-video-create.test.ts | 7 ++++++- apps/web/__tests__/unit/loom-import.test.ts | 6 ++++++ apps/web/actions/loom.ts | 7 +++++-- .../actions/video/create-for-processing.ts | 11 ++++++++--- apps/web/actions/video/upload.ts | 11 ++++++++--- apps/web/app/api/desktop/[...route]/video.ts | 6 ++++-- apps/web/app/api/mobile/[...route]/route.ts | 19 ++++++++++++++++--- apps/web/app/api/v1/[...route]/route.ts | 13 +++++++++++-- packages/web-backend/src/Videos/index.ts | 8 +++++++- 9 files changed, 71 insertions(+), 17 deletions(-) diff --git a/apps/web/__tests__/unit/desktop-video-create.test.ts b/apps/web/__tests__/unit/desktop-video-create.test.ts index 26b3167431..dc129b800b 100644 --- a/apps/web/__tests__/unit/desktop-video-create.test.ts +++ b/apps/web/__tests__/unit/desktop-video-create.test.ts @@ -64,6 +64,7 @@ vi.mock("@cap/web-backend", () => ({ getOrganizationWritableAccess: vi.fn(), getS3WritableAccessForUser: vi.fn(), }, + resolveNewVideoDefaults: vi.fn(), })); vi.mock("@/lib/server", () => ({ @@ -92,7 +93,7 @@ vi.mock("drizzle-orm", () => ({ })); const mockGetCurrentUser = getCurrentUser as ReturnType; -const { Storage } = await import("@cap/web-backend"); +const { Storage, resolveNewVideoDefaults } = await import("@cap/web-backend"); const effectLike = (value: T) => ({ pipe: (fn: (value: T) => unknown) => fn(value), @@ -140,6 +141,10 @@ function stubStorage() { storageIntegrationId: Option.none(), }), ); + (resolveNewVideoDefaults as ReturnType).mockResolvedValue({ + public: true, + password: null, + }); } describe("GET /create", () => { diff --git a/apps/web/__tests__/unit/loom-import.test.ts b/apps/web/__tests__/unit/loom-import.test.ts index 765347c06a..df8b47ac58 100644 --- a/apps/web/__tests__/unit/loom-import.test.ts +++ b/apps/web/__tests__/unit/loom-import.test.ts @@ -6,6 +6,7 @@ const valuesMock = vi.fn(); const startMock = vi.fn(); const revalidatePathMock = vi.fn(); const storageGetWritableAccessForUserMock = vi.hoisted(() => vi.fn()); +const resolveNewVideoDefaultsMock = vi.hoisted(() => vi.fn()); const checkRateLimitMock = vi.hoisted(() => vi.fn()); const headersMock = vi.hoisted(() => vi.fn()); const getOrganizationAccessMock = vi.hoisted(() => vi.fn()); @@ -125,6 +126,7 @@ vi.mock("@cap/web-backend", () => ({ Storage: { getWritableAccessForUser: storageGetWritableAccessForUserMock, }, + resolveNewVideoDefaults: resolveNewVideoDefaultsMock, })); vi.mock("@cap/web-domain", () => ({ @@ -230,6 +232,10 @@ describe("importFromLoom", () => { storageIntegrationId: Option.none(), }), ); + resolveNewVideoDefaultsMock.mockResolvedValue({ + public: true, + password: null, + }); mockGetCurrentUser.mockResolvedValue({ id: "user-123", }); diff --git a/apps/web/actions/loom.ts b/apps/web/actions/loom.ts index 75f3672a2a..7ab783dbd6 100644 --- a/apps/web/actions/loom.ts +++ b/apps/web/actions/loom.ts @@ -16,7 +16,7 @@ import { } from "@cap/database/schema"; import { buildEnv, NODE_ENV, serverEnv } from "@cap/env"; import { dub, userIsPro } from "@cap/utils"; -import { Storage } from "@cap/web-backend"; +import { resolveNewVideoDefaults, Storage } from "@cap/web-backend"; import { type Organisation, Space, @@ -387,6 +387,8 @@ async function importLoomVideoForOwner({ videoName || `Loom Import - ${new Date().toLocaleDateString("en-US", { day: "numeric", month: "long", year: "numeric" })}`; + const videoDefaults = await resolveNewVideoDefaults(db(), orgId); + await db() .insert(videos) .values({ @@ -397,7 +399,8 @@ async function importLoomVideoForOwner({ source: { type: "webMP4" as const }, bucket: Option.getOrNull(writable.bucketId), storageIntegrationId: Option.getOrNull(writable.storageIntegrationId), - public: serverEnv().CAP_VIDEOS_DEFAULT_PUBLIC, + public: videoDefaults.public, + password: videoDefaults.password, ...(oembedMeta?.duration ? { duration: oembedMeta.duration } : {}), ...(oembedMeta?.width ? { width: oembedMeta.width } : {}), ...(oembedMeta?.height ? { height: oembedMeta.height } : {}), diff --git a/apps/web/actions/video/create-for-processing.ts b/apps/web/actions/video/create-for-processing.ts index a3acda737b..1267c6a311 100644 --- a/apps/web/actions/video/create-for-processing.ts +++ b/apps/web/actions/video/create-for-processing.ts @@ -4,9 +4,11 @@ import { db } from "@cap/database"; import { getCurrentUser } from "@cap/database/auth/session"; import { nanoId } from "@cap/database/helpers"; import { videos, videoUploads } from "@cap/database/schema"; -import { serverEnv } from "@cap/env"; import { userIsPro } from "@cap/utils"; -import { Storage as StorageService } from "@cap/web-backend"; +import { + resolveNewVideoDefaults, + Storage as StorageService, +} from "@cap/web-backend"; import { type Folder, type Organisation, @@ -86,6 +88,8 @@ export async function createVideoForServerProcessing({ orgId, ).pipe(runPromise); + const videoDefaults = await resolveNewVideoDefaults(db(), orgId); + await db() .insert(videos) .values({ @@ -96,7 +100,8 @@ export async function createVideoForServerProcessing({ source: { type: "webMP4" as const }, bucket: Option.getOrNull(uploadResult.bucketId), storageIntegrationId: Option.getOrNull(uploadResult.storageIntegrationId), - public: serverEnv().CAP_VIDEOS_DEFAULT_PUBLIC, + public: videoDefaults.public, + password: videoDefaults.password, ...(folderId ? { folderId } : {}), }); diff --git a/apps/web/actions/video/upload.ts b/apps/web/actions/video/upload.ts index 43fe9d1fa7..dfe09ad7e2 100644 --- a/apps/web/actions/video/upload.ts +++ b/apps/web/actions/video/upload.ts @@ -4,9 +4,11 @@ import { db } from "@cap/database"; import { getCurrentUser } from "@cap/database/auth/session"; import { nanoId } from "@cap/database/helpers"; import { videos, videoUploads } from "@cap/database/schema"; -import { serverEnv } from "@cap/env"; import { userIsPro } from "@cap/utils"; -import { Storage as StorageService } from "@cap/web-backend"; +import { + resolveNewVideoDefaults, + Storage as StorageService, +} from "@cap/web-backend"; import { type Folder, type Organisation, @@ -212,6 +214,8 @@ export async function createVideoAndGetUploadUrl({ organizationId: orgId, }); + const videoDefaults = await resolveNewVideoDefaults(db(), orgId); + const videoData = { id: idToUse, name: `Cap ${ @@ -223,7 +227,8 @@ export async function createVideoAndGetUploadUrl({ isScreenshot, bucket: Option.getOrNull(bucketId), storageIntegrationId: Option.getOrNull(storageIntegrationId), - public: serverEnv().CAP_VIDEOS_DEFAULT_PUBLIC, + public: videoDefaults.public, + password: videoDefaults.password, ...(folderId ? { folderId } : {}), }; diff --git a/apps/web/app/api/desktop/[...route]/video.ts b/apps/web/app/api/desktop/[...route]/video.ts index 2db9602e7e..d236a7d300 100644 --- a/apps/web/app/api/desktop/[...route]/video.ts +++ b/apps/web/app/api/desktop/[...route]/video.ts @@ -13,7 +13,7 @@ import { import type { VideoMetadata } from "@cap/database/types"; import { serverEnv } from "@cap/env"; import { userIsPro } from "@cap/utils"; -import { Storage } from "@cap/web-backend"; +import { resolveNewVideoDefaults, Storage } from "@cap/web-backend"; import { Organisation, Video } from "@cap/web-domain"; import { zValidator } from "@hono/zod-validator"; import { and, count, eq, lte } from "drizzle-orm"; @@ -262,6 +262,7 @@ app.get( } const idToUse = Video.VideoId.make(nanoId()); + const videoDefaults = await resolveNewVideoDefaults(db(), videoOrgId); const videoName = name ?? @@ -311,7 +312,8 @@ app.get( isScreenshot, bucket: Option.getOrNull(writable.bucketId), storageIntegrationId: Option.getOrNull(writable.storageIntegrationId), - public: serverEnv().CAP_VIDEOS_DEFAULT_PUBLIC, + public: videoDefaults.public, + password: videoDefaults.password, duration: durationInSecs, width, height, diff --git a/apps/web/app/api/mobile/[...route]/route.ts b/apps/web/app/api/mobile/[...route]/route.ts index 084b7f1a0e..274075bdba 100644 --- a/apps/web/app/api/mobile/[...route]/route.ts +++ b/apps/web/app/api/mobile/[...route]/route.ts @@ -13,6 +13,7 @@ import { findScreenshotObjectKey, getCurrentUser, ImageUploads, + resolveNewVideoDefaults, Storage, Videos, VideosRepo, @@ -2338,6 +2339,9 @@ const importLoom = Effect.fn("Mobile.importLoom")(function* ( ); const videoId = Video.VideoId.make(nanoId()); const now = new Date(); + const videoDefaults = yield* database.use((db) => + resolveNewVideoDefaults(db, user.activeOrganizationId), + ); const importState = yield* database.use((db) => db.transaction(async (tx) => { const [existing] = await tx @@ -2384,7 +2388,8 @@ const importLoom = Effect.fn("Mobile.importLoom")(function* ( source: { type: "webMP4" }, bucket: Option.getOrNull(writable.bucketId), storageIntegrationId: Option.getOrNull(writable.storageIntegrationId), - public: serverEnv().CAP_VIDEOS_DEFAULT_PUBLIC, + public: videoDefaults.public, + password: videoDefaults.password, duration: download.durationSeconds, width: download.width, height: download.height, @@ -2467,11 +2472,15 @@ const createUpload = Effect.fn("Mobile.createUpload")(function* ( user.id, organizationId, ); + const videoDefaults = yield* database.use((db) => + resolveNewVideoDefaults(db, organizationId), + ); const videoId = yield* repo.create({ ownerId: user.id, orgId: organizationId, name: getUploadTitle(input.fileName), - public: serverEnv().CAP_VIDEOS_DEFAULT_PUBLIC, + public: videoDefaults.public, + password: videoDefaults.password ?? undefined, source: { type: "webMP4" }, bucketId: writable.bucketId, storageIntegrationId: writable.storageIntegrationId, @@ -2562,11 +2571,15 @@ const createRecording = Effect.fn("Mobile.createRecording")(function* ( user.id, organizationId, ); + const videoDefaults = yield* database.use((db) => + resolveNewVideoDefaults(db, organizationId), + ); const videoId = yield* repo.create({ ownerId: user.id, orgId: organizationId, name: getUploadTitle(input.fileName), - public: serverEnv().CAP_VIDEOS_DEFAULT_PUBLIC, + public: videoDefaults.public, + password: videoDefaults.password ?? undefined, source: { type: "desktopSegments" }, bucketId: writable.bucketId, storageIntegrationId: writable.storageIntegrationId, diff --git a/apps/web/app/api/v1/[...route]/route.ts b/apps/web/app/api/v1/[...route]/route.ts index ca9726bbc7..b64a9e5eed 100644 --- a/apps/web/app/api/v1/[...route]/route.ts +++ b/apps/web/app/api/v1/[...route]/route.ts @@ -28,6 +28,7 @@ import { getGoogleDriveUserEmail, ImageUploads, resolveEffectiveVideoRules, + resolveNewVideoDefaults, Storage, Videos, } from "@cap/web-backend"; @@ -2349,6 +2350,9 @@ const queueAgentLoomImport = Effect.fn("Agent.queueLoomImport")( ); const rawFileKey = `${ownerId}/${videoId}/raw-upload.mp4`; const now = new Date(); + const videoDefaults = yield* database.use((db) => + resolveNewVideoDefaults(db, input.organizationId), + ); const importState = yield* database.use((db) => db.transaction(async (tx) => { const [existingImport] = await tx @@ -2385,7 +2389,8 @@ const queueAgentLoomImport = Effect.fn("Agent.queueLoomImport")( storageIntegrationId: Option.getOrNull( writable.storageIntegrationId, ), - public: serverEnv().CAP_VIDEOS_DEFAULT_PUBLIC, + public: videoDefaults.public, + password: videoDefaults.password, duration: download.durationSeconds, width: download.width, height: download.height, @@ -6578,6 +6583,9 @@ const AgentManagementHandlersLive = HttpApiBuilder.group( if (title.length > 255) { return yield* badRequest(requestId, "Upload title is too long"); } + const videoDefaults = yield* database.use((db) => + resolveNewVideoDefaults(db, organizationId), + ); const state = yield* runAgentMutation({ principal, operation: "create_upload", @@ -6598,7 +6606,8 @@ const AgentManagementHandlersLive = HttpApiBuilder.group( writable.storageIntegrationId, ), folderId: payload.folderId ?? null, - public: serverEnv().CAP_VIDEOS_DEFAULT_PUBLIC, + public: videoDefaults.public, + password: videoDefaults.password, duration: payload.durationSeconds, width: payload.width, height: payload.height, diff --git a/packages/web-backend/src/Videos/index.ts b/packages/web-backend/src/Videos/index.ts index 2efc876e43..2a483a6f66 100644 --- a/packages/web-backend/src/Videos/index.ts +++ b/packages/web-backend/src/Videos/index.ts @@ -9,6 +9,7 @@ import type { Schema } from "effect/Schema"; import { Database } from "../Database.ts"; import { Storage as StorageService } from "../Storage/index.ts"; import { Tinybird } from "../Tinybird/index.ts"; +import { resolveNewVideoDefaults } from "./NewVideoDefaults.ts"; import { VideosPolicy } from "./VideosPolicy.ts"; import type { CreateVideoInput as RepoCreateVideoInput } from "./VideosRepo.ts"; import { VideosRepo } from "./VideosRepo.ts"; @@ -477,11 +478,16 @@ export class Videos extends Effect.Service()("Videos", { }, )} ${now.getFullYear()}`; + const videoDefaults = yield* db.use((db) => + resolveNewVideoDefaults(db, input.orgId), + ); + const createData: RepoCreateVideoInput = { ownerId: user.id, orgId: input.orgId, name: `Cap Recording - ${formattedDate}`, - public: serverEnv().CAP_VIDEOS_DEFAULT_PUBLIC, + public: videoDefaults.public, + password: videoDefaults.password ?? undefined, source: { type: "webMP4" }, bucketId, storageIntegrationId, From 8e6b2b680a7fff6a2bbaf9d85b0c7bffd484be63 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:42:19 +0100 Subject: [PATCH 04/11] feat(api): expose defaultVideoPublic in agent organization settings --- apps/web/lib/agent-management.ts | 2 ++ packages/web-domain/src/Agent.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apps/web/lib/agent-management.ts b/apps/web/lib/agent-management.ts index 4fac2b212a..bdbebfa56a 100644 --- a/apps/web/lib/agent-management.ts +++ b/apps/web/lib/agent-management.ts @@ -65,6 +65,7 @@ export const agentOrganizationSettings = ( shareableLinkUseOrganizationIcon?: boolean; aiGenerationLanguage?: (typeof Agent.AgentAiGenerationLanguage)["Type"]; defaultPlaybackSpeed?: number; + defaultVideoPublic?: boolean; } | null, ): (typeof Agent.AgentOrganizationSettings)["Type"] => ({ disableSummary: settings?.disableSummary ?? null, @@ -78,6 +79,7 @@ export const agentOrganizationSettings = ( settings?.shareableLinkUseOrganizationIcon ?? null, aiGenerationLanguage: settings?.aiGenerationLanguage ?? null, defaultPlaybackSpeed: settings?.defaultPlaybackSpeed ?? null, + defaultVideoPublic: settings?.defaultVideoPublic ?? null, }); const scopedAction = ( diff --git a/packages/web-domain/src/Agent.ts b/packages/web-domain/src/Agent.ts index 991f058a83..60f3db9a9b 100644 --- a/packages/web-domain/src/Agent.ts +++ b/packages/web-domain/src/Agent.ts @@ -591,6 +591,7 @@ export const AgentOrganizationSettings = Schema.Struct({ shareableLinkUseOrganizationIcon: Schema.NullOr(Schema.Boolean), aiGenerationLanguage: Schema.NullOr(AgentAiGenerationLanguage), defaultPlaybackSpeed: Schema.NullOr(Schema.Number), + defaultVideoPublic: Schema.NullOr(Schema.Boolean), }); export const AgentOrganization = Schema.Struct({ @@ -656,6 +657,7 @@ export const AgentOrganizationSettingsInput = Schema.Struct({ shareableLinkUseOrganizationIcon: Schema.optional(Schema.Boolean), aiGenerationLanguage: Schema.optional(AgentAiGenerationLanguage), defaultPlaybackSpeed: Schema.optional(Schema.Number), + defaultVideoPublic: Schema.optional(Schema.Boolean), }); export const AgentOrganizationInviteInput = Schema.Struct({ From 1854cb93e033f063af736fc1bcf2199de9599fb2 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:42:19 +0100 Subject: [PATCH 05/11] feat(web): default sharing controls in organization settings --- .../organization/default-video-password.ts | 71 ++++++ apps/web/actions/organization/settings.ts | 1 + .../web/app/(org)/dashboard/dashboard-data.ts | 8 +- .../components/CapSettingsCard.tsx | 240 +++++++++++++++++- 4 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 apps/web/actions/organization/default-video-password.ts diff --git a/apps/web/actions/organization/default-video-password.ts b/apps/web/actions/organization/default-video-password.ts new file mode 100644 index 0000000000..c6aeca4596 --- /dev/null +++ b/apps/web/actions/organization/default-video-password.ts @@ -0,0 +1,71 @@ +"use server"; + +import { db } from "@cap/database"; +import { getCurrentUser } from "@cap/database/auth/session"; +import { hashPassword } from "@cap/database/crypto"; +import { organizations } from "@cap/database/schema"; +import { eq } from "drizzle-orm"; +import { revalidatePath } from "next/cache"; +import { requireOrganizationSettingsManager } from "./authorization"; + +function revalidateOrganizationSettingsPaths() { + revalidatePath("/dashboard/caps"); + revalidatePath("/dashboard/settings/organization"); + revalidatePath("/dashboard/settings/organization/preferences"); +} + +export async function setOrganizationDefaultVideoPassword(password: string) { + try { + const user = await getCurrentUser(); + + if (!user?.activeOrganizationId) throw new Error("Unauthorized"); + + if (typeof password !== "string" || password.trim().length === 0) + throw new Error("Password is required"); + + if (password.length > 255) throw new Error("Password is too long"); + + await requireOrganizationSettingsManager( + user.id, + user.activeOrganizationId, + ); + + const hashed = await hashPassword(password); + await db() + .update(organizations) + .set({ defaultVideoPassword: hashed }) + .where(eq(organizations.id, user.activeOrganizationId)); + + revalidateOrganizationSettingsPaths(); + + return { success: true, value: "Default password updated successfully" }; + } catch (error) { + console.error("Error setting organization default video password:", error); + return { success: false, error: "Failed to update default password" }; + } +} + +export async function removeOrganizationDefaultVideoPassword() { + try { + const user = await getCurrentUser(); + + if (!user?.activeOrganizationId) throw new Error("Unauthorized"); + + await requireOrganizationSettingsManager( + user.id, + user.activeOrganizationId, + ); + + await db() + .update(organizations) + .set({ defaultVideoPassword: null }) + .where(eq(organizations.id, user.activeOrganizationId)); + + revalidateOrganizationSettingsPaths(); + + return { success: true, value: "Default password removed successfully" }; + } catch (error) { + console.error("Error removing organization default video password:", error); + return { success: false, error: "Failed to remove default password" }; + } +} diff --git a/apps/web/actions/organization/settings.ts b/apps/web/actions/organization/settings.ts index 1d8abe0aab..64af8b69b3 100644 --- a/apps/web/actions/organization/settings.ts +++ b/apps/web/actions/organization/settings.ts @@ -25,6 +25,7 @@ type OrganizationSettingsInput = { shareableLinkUseOrganizationIcon?: boolean; aiGenerationLanguage?: AiGenerationLanguage; defaultPlaybackSpeed?: number; + defaultVideoPublic?: boolean; }; const proOrganizationSettingKeys = [ diff --git a/apps/web/app/(org)/dashboard/dashboard-data.ts b/apps/web/app/(org)/dashboard/dashboard-data.ts index db913c12dd..8af374e8da 100644 --- a/apps/web/app/(org)/dashboard/dashboard-data.ts +++ b/apps/web/app/(org)/dashboard/dashboard-data.ts @@ -30,10 +30,11 @@ import { selectProSeatProvider } from "@/utils/organization"; export type Organization = { organization: Omit< typeof organizations.$inferSelect, - "iconUrl" | "shareableLinkIconUrl" + "iconUrl" | "shareableLinkIconUrl" | "defaultVideoPassword" > & { iconUrl: ImageUpload.ImageUrl | null; shareableLinkIconUrl: ImageUpload.ImageUrl | null; + hasDefaultVideoPassword: boolean; }; members: (typeof organizationMembers.$inferSelect & { user: Pick< @@ -427,9 +428,12 @@ export async function getDashboardData(user: typeof userSelectProps) { (memberCountResult[0]?.value || 0) + (inviteCountResult[0]?.value || 0); + const { defaultVideoPassword, ...clientOrganization } = organization; + return { organization: { - ...organization, + ...clientOrganization, + hasDefaultVideoPassword: defaultVideoPassword !== null, iconUrl: organization.iconUrl ? yield* iconImages.resolveImageUrl(organization.iconUrl) : null, diff --git a/apps/web/app/(org)/dashboard/settings/organization/components/CapSettingsCard.tsx b/apps/web/app/(org)/dashboard/settings/organization/components/CapSettingsCard.tsx index 897385d7e1..d12983d7d5 100644 --- a/apps/web/app/(org)/dashboard/settings/organization/components/CapSettingsCard.tsx +++ b/apps/web/app/(org)/dashboard/settings/organization/components/CapSettingsCard.tsx @@ -1,6 +1,14 @@ "use client"; -import { Card, CardDescription, CardHeader, CardTitle, Switch } from "@cap/ui"; +import { + Button, + Card, + CardDescription, + CardHeader, + CardTitle, + Input, + Switch, +} from "@cap/ui"; import { AI_GENERATION_LANGUAGE_AUTO, AI_GENERATION_LANGUAGES, @@ -8,11 +16,17 @@ import { getAiGenerationLanguageName, isAiGenerationLanguage, } from "@cap/web-domain"; +import { useMutation } from "@tanstack/react-query"; import { useDebounce } from "@uidotdev/usehooks"; import clsx from "clsx"; -import { ChevronDown, Gauge, Globe } from "lucide-react"; +import { ChevronDown, Eye, Gauge, Globe, Lock } from "lucide-react"; +import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { toast } from "sonner"; +import { + removeOrganizationDefaultVideoPassword, + setOrganizationDefaultVideoPassword, +} from "@/actions/organization/default-video-password"; import { updateOrganizationSettings } from "@/actions/organization/settings"; import { DEFAULT_PLAYBACK_SPEED, PLAYBACK_SPEEDS } from "@/lib/playback-speed"; import { useDashboardContext } from "../../../Contexts"; @@ -88,6 +102,11 @@ const languageOptions = Object.entries(AI_GENERATION_LANGUAGES) as [ string, ][]; +const visibilityOptions: Array<{ label: string; value: boolean }> = [ + { label: "Anyone with the link", value: true }, + { label: "Private", value: false }, +]; + const mergeSettings = ( settings?: OrganizationSettings | null, ): OrganizationSettings => ({ @@ -99,11 +118,22 @@ const mergeSettings = ( }); const CapSettingsCard = () => { - const { user, organizationSettings } = useDashboardContext(); + const { user, organizationSettings, activeOrganization } = + useDashboardContext(); + const router = useRouter(); const initialSettings = mergeSettings(organizationSettings); const [settings, setSettings] = useState(initialSettings); const [showLanguageMenu, setShowLanguageMenu] = useState(false); + const hasDefaultVideoPassword = Boolean( + activeOrganization?.organization.hasDefaultVideoPassword, + ); + const [defaultPasswordEnabled, setDefaultPasswordEnabled] = useState( + hasDefaultVideoPassword, + ); + const [isChangingDefaultPassword, setIsChangingDefaultPassword] = + useState(false); + const [defaultPassword, setDefaultPassword] = useState(""); const lastSavedSettings = useRef(initialSettings); const languageMenuRef = useRef(null); @@ -118,6 +148,12 @@ const CapSettingsCard = () => { lastSavedSettings.current = next; }, [organizationSettings]); + useEffect(() => { + setDefaultPasswordEnabled(hasDefaultVideoPassword); + setIsChangingDefaultPassword(false); + setDefaultPassword(""); + }, [hasDefaultVideoPassword]); + useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( @@ -167,6 +203,15 @@ const CapSettingsCard = () => { return; } + if (changedKey === "defaultVideoPublic") { + toast.success( + debouncedUpdateSettings.defaultVideoPublic === false + ? "New caps will be private" + : "New caps will be shared with anyone with the link", + ); + return; + } + if (changedKey === "defaultPlaybackSpeed") { toast.success( `Default playback speed set to ${ @@ -235,6 +280,72 @@ const CapSettingsCard = () => { const selectedSpeed = settings.defaultPlaybackSpeed ?? DEFAULT_PLAYBACK_SPEED; + const handleVisibilityChange = (isPublic: boolean) => { + setSettings((prev) => ({ + ...prev, + defaultVideoPublic: isPublic, + })); + }; + + const defaultVideoPublic = settings.defaultVideoPublic !== false; + + const saveDefaultPassword = useMutation({ + mutationFn: async () => { + const result = await setOrganizationDefaultVideoPassword(defaultPassword); + if (!result.success) + throw new Error(result.error ?? "Failed to update default password"); + return result.value; + }, + onSuccess: () => { + toast.success("Default password updated"); + setDefaultPassword(""); + setIsChangingDefaultPassword(false); + router.refresh(); + }, + onError: (error) => { + toast.error(error.message); + }, + }); + + const removeDefaultPassword = useMutation({ + mutationFn: async () => { + const result = await removeOrganizationDefaultVideoPassword(); + if (!result.success) + throw new Error(result.error ?? "Failed to remove default password"); + return result.value; + }, + onSuccess: () => { + toast.success("Default password removed"); + setDefaultPassword(""); + setIsChangingDefaultPassword(false); + setDefaultPasswordEnabled(false); + router.refresh(); + }, + onError: (error) => { + toast.error(error.message); + setDefaultPasswordEnabled(hasDefaultVideoPassword); + }, + }); + + const defaultPasswordPending = + saveDefaultPassword.isPending || removeDefaultPassword.isPending; + + const handleDefaultPasswordToggle = (checked: boolean) => { + if (checked) { + setDefaultPasswordEnabled(true); + return; + } + + if (hasDefaultVideoPassword) { + removeDefaultPassword.mutate(); + return; + } + + setDefaultPasswordEnabled(false); + setIsChangingDefaultPassword(false); + setDefaultPassword(""); + }; + const handleLanguageChange = (language: AiGenerationLanguage) => { if (!isAiGenerationLanguage(language)) { return; @@ -292,6 +403,129 @@ const CapSettingsCard = () => { ))} +
+

Default sharing

+ +
+
+
+ +

Who can view new caps

+
+

+ Applies to new caps. Sharing can still be changed on each cap. +

+
+
+ {visibilityOptions.map((option) => ( + + ))} +
+
+ +
+
+
+
+ +

+ Password protect new caps +

+
+

+ New caps get this password automatically. Viewers need it to + open the link. +

+
+ +
+ + {defaultPasswordEnabled && + (hasDefaultVideoPassword && !isChangingDefaultPassword ? ( +
+

+ A default password is set. It cannot be shown again. +

+
+ + +
+
+ ) : ( +
+ setDefaultPassword(e.target.value)} + /> +
+ + {hasDefaultVideoPassword && ( + + )} +
+
+ ))} +
+
+
From 0a9733543364a4eb35c28785150e6290f8ca9e52 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:09:06 +0100 Subject: [PATCH 06/11] fix(web): show instance fallback for unset default visibility --- apps/web/app/(org)/dashboard/Contexts.tsx | 3 +++ apps/web/app/(org)/dashboard/DashboardContext.ts | 1 + apps/web/app/(org)/dashboard/layout.tsx | 2 ++ .../organization/components/CapSettingsCard.tsx | 11 ++++++++--- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/web/app/(org)/dashboard/Contexts.tsx b/apps/web/app/(org)/dashboard/Contexts.tsx index 40f5ac3b06..90459a271c 100644 --- a/apps/web/app/(org)/dashboard/Contexts.tsx +++ b/apps/web/app/(org)/dashboard/Contexts.tsx @@ -29,6 +29,7 @@ export function DashboardContexts({ spacesData, userCapsCount, organizationSettings, + instanceVideoDefaultPublic, userPreferences, anyNewNotifications, initialTheme, @@ -41,6 +42,7 @@ export function DashboardContexts({ spacesData: SharedContext["spacesData"]; userCapsCount: SharedContext["userCapsCount"]; organizationSettings: SharedContext["organizationSettings"]; + instanceVideoDefaultPublic: SharedContext["instanceVideoDefaultPublic"]; userPreferences: SharedContext["userPreferences"]; anyNewNotifications: boolean; initialTheme: ITheme; @@ -149,6 +151,7 @@ export function DashboardContexts({ anyNewNotifications, userPreferences, organizationSettings, + instanceVideoDefaultPublic, userSpaces, sharedSpaces, activeSpace, diff --git a/apps/web/app/(org)/dashboard/DashboardContext.ts b/apps/web/app/(org)/dashboard/DashboardContext.ts index b4c0e6e7a3..9845baf1a7 100644 --- a/apps/web/app/(org)/dashboard/DashboardContext.ts +++ b/apps/web/app/(org)/dashboard/DashboardContext.ts @@ -21,6 +21,7 @@ export type SharedContext = { organizationData: Organization[] | null; activeOrganization: Organization | null; organizationSettings: OrganizationSettings | null; + instanceVideoDefaultPublic: boolean; spacesData: Spaces[] | null; userSpaces: Spaces[] | null; sharedSpaces: Spaces[] | null; diff --git a/apps/web/app/(org)/dashboard/layout.tsx b/apps/web/app/(org)/dashboard/layout.tsx index dde0a568be..b11c7a3032 100644 --- a/apps/web/app/(org)/dashboard/layout.tsx +++ b/apps/web/app/(org)/dashboard/layout.tsx @@ -1,6 +1,7 @@ import { db } from "@cap/database"; import { getCurrentUser } from "@cap/database/auth/session"; import { organizationInvites } from "@cap/database/schema"; +import { serverEnv } from "@cap/env"; import { and, eq } from "drizzle-orm"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; @@ -96,6 +97,7 @@ export default async function DashboardLayout({ { - const { user, organizationSettings, activeOrganization } = - useDashboardContext(); + const { + user, + organizationSettings, + activeOrganization, + instanceVideoDefaultPublic, + } = useDashboardContext(); const router = useRouter(); const initialSettings = mergeSettings(organizationSettings); const [settings, setSettings] = @@ -287,7 +291,8 @@ const CapSettingsCard = () => { })); }; - const defaultVideoPublic = settings.defaultVideoPublic !== false; + const defaultVideoPublic = + settings.defaultVideoPublic ?? instanceVideoDefaultPublic; const saveDefaultPassword = useMutation({ mutationFn: async () => { From f27233be2ef4205cb914110d9c44dbe2138c4d08 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:56:16 +0100 Subject: [PATCH 07/11] test: cover org sharing defaults wiring at creation --- .../unit/desktop-video-create.test.ts | 31 +++++++++ apps/web/__tests__/unit/loom-import.test.ts | 63 +++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/apps/web/__tests__/unit/desktop-video-create.test.ts b/apps/web/__tests__/unit/desktop-video-create.test.ts index dc129b800b..5ead2bc89a 100644 --- a/apps/web/__tests__/unit/desktop-video-create.test.ts +++ b/apps/web/__tests__/unit/desktop-video-create.test.ts @@ -231,6 +231,36 @@ describe("GET /create", () => { }); }); + it("applies the org's sharing defaults to the created video", async () => { + mockGetCurrentUser.mockResolvedValue({ + id: "user-1", + email: "someone@cap.test", + defaultOrgId: "org-1", + activeOrganizationId: "org-1", + }); + mockDb.where + .mockResolvedValueOnce([ + { id: "org-1", name: "Acme", createdAt: new Date() }, + ]) + .mockResolvedValueOnce([]) + .mockResolvedValueOnce([{ count: 5 }]); + (resolveNewVideoDefaults as ReturnType).mockResolvedValue({ + public: false, + password: "org-default-hash", + }); + + const response = await app.request("https://cap.test/create"); + + expect(response.status).toBe(200); + expect(resolveNewVideoDefaults).toHaveBeenCalledWith(mockDb, "org-1"); + expect(insertedValues(schema.videos)).toMatchObject({ + orgId: "org-1", + ownerId: "user-1", + public: false, + password: "org-default-hash", + }); + }); + it("heals a dangling defaultOrgId when the user has no remaining orgs", async () => { mockGetCurrentUser.mockResolvedValue({ id: "user-1", @@ -345,5 +375,6 @@ describe("GET /create", () => { orgId: "org-2", ownerId: "user-1", }); + expect(resolveNewVideoDefaults).toHaveBeenCalledWith(mockDb, "org-2"); }); }); diff --git a/apps/web/__tests__/unit/loom-import.test.ts b/apps/web/__tests__/unit/loom-import.test.ts index df8b47ac58..cf688a5e3d 100644 --- a/apps/web/__tests__/unit/loom-import.test.ts +++ b/apps/web/__tests__/unit/loom-import.test.ts @@ -488,6 +488,69 @@ describe("importFromLoom", () => { expect(revalidatePathMock).toHaveBeenCalledWith("/dashboard/caps"); }); + it("applies the org's sharing defaults to the imported video", async () => { + whereMock.mockResolvedValueOnce([]).mockResolvedValueOnce(undefined); + + resolveNewVideoDefaultsMock.mockResolvedValue({ + public: false, + password: "org-default-hash", + }); + + const fetchMock = vi.mocked(fetch); + fetchMock.mockImplementation(async (input) => { + const url = typeof input === "string" ? input : input.toString(); + + if (url.includes("/transcoded-url")) { + return { + ok: true, + status: 200, + text: async () => + JSON.stringify({ url: "https://cdn.loom.com/video.mp4" }), + } as Response; + } + + if (url === "https://www.loom.com/graphql") { + return { + ok: true, + json: async () => ({ + data: { getVideo: { name: "Imported video" } }, + }), + } as Response; + } + + if (url.includes("/v1/oembed")) { + return { + ok: true, + json: async () => ({ duration: 42, width: 1920, height: 1080 }), + } as Response; + } + + throw new Error(`Unexpected fetch: ${url}`); + }); + + const { importFromLoom } = await import("@/actions/loom"); + + const result = await importFromLoom({ + loomUrl: "https://www.loom.com/share/loom-abc1234567", + orgId: "org-1" as never, + }); + + expect(result).toEqual({ + success: true, + videoId: "video-123", + }); + expect(resolveNewVideoDefaultsMock).toHaveBeenCalledWith( + expect.anything(), + "org-1", + ); + expect(valuesMock).toHaveBeenCalledWith( + expect.objectContaining({ + public: false, + password: "org-default-hash", + }), + ); + }); + it("rejects a CSV import when the current user is not an organization admin or owner", async () => { getOrganizationAccessMock.mockResolvedValueOnce({ id: "org-1", From ce267acd096e2dbce508075c552d5c9613d17fd4 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:57:08 +0100 Subject: [PATCH 08/11] feat(web): redesign organization preferences with settings row system --- .../components/CapSettingsCard.tsx | 498 +++++++----------- .../organization/components/SettingsRows.tsx | 125 +++++ 2 files changed, 328 insertions(+), 295 deletions(-) create mode 100644 apps/web/app/(org)/dashboard/settings/organization/components/SettingsRows.tsx diff --git a/apps/web/app/(org)/dashboard/settings/organization/components/CapSettingsCard.tsx b/apps/web/app/(org)/dashboard/settings/organization/components/CapSettingsCard.tsx index 0f2c7319a8..5d4ca0bdba 100644 --- a/apps/web/app/(org)/dashboard/settings/organization/components/CapSettingsCard.tsx +++ b/apps/web/app/(org)/dashboard/settings/organization/components/CapSettingsCard.tsx @@ -1,14 +1,6 @@ "use client"; -import { - Button, - Card, - CardDescription, - CardHeader, - CardTitle, - Input, - Switch, -} from "@cap/ui"; +import { Button, Input, Switch } from "@cap/ui"; import { AI_GENERATION_LANGUAGE_AUTO, AI_GENERATION_LANGUAGES, @@ -18,8 +10,6 @@ import { } from "@cap/web-domain"; import { useMutation } from "@tanstack/react-query"; import { useDebounce } from "@uidotdev/usehooks"; -import clsx from "clsx"; -import { ChevronDown, Eye, Gauge, Globe, Lock } from "lucide-react"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { toast } from "sonner"; @@ -31,6 +21,7 @@ import { updateOrganizationSettings } from "@/actions/organization/settings"; import { DEFAULT_PLAYBACK_SPEED, PLAYBACK_SPEEDS } from "@/lib/playback-speed"; import { useDashboardContext } from "../../../Contexts"; import type { OrganizationSettings } from "../../../dashboard-data"; +import { type SelectOption, SettingRow, SettingSelect } from "./SettingsRows"; const defaultSettings: OrganizationSettings = { disableComments: false, @@ -57,42 +48,42 @@ const options: Array<{ pro?: boolean; }> = [ { - label: "Enable comments", + label: "Comments", value: "disableComments", - description: "Allow viewers to comment on caps", + description: "Allow viewers to comment", }, { - label: "Enable summary", - value: "disableSummary", - description: "Show AI-generated summary (requires transcript)", - pro: true, + label: "Reactions", + value: "disableReactions", + description: "Allow viewers to react", }, { - label: "Enable captions", - value: "disableCaptions", - description: "Allow viewers to use captions for caps", + label: "Transcript", + value: "disableTranscript", + description: "Also required for chapters and summary", + pro: true, }, { - label: "Enable chapters", + label: "Chapters", value: "disableChapters", - description: "Show AI-generated chapters (requires transcript)", + description: "AI generated, requires transcript", pro: true, }, { - label: "Enable reactions", - value: "disableReactions", - description: "Allow viewers to react to caps", + label: "Summary", + value: "disableSummary", + description: "AI generated, requires transcript", + pro: true, }, { - label: "Enable transcript", - value: "disableTranscript", - description: "Enabling this also allows chapters and summary", - pro: true, + label: "Captions", + value: "disableCaptions", + description: "Let viewers turn on captions", }, { - label: "Show Cap logo", + label: "Cap logo", value: "hideShareableLinkCapLogo", - description: "Show Cap branding at the top of shareable links", + description: "Show Cap branding on share pages", pro: true, }, ]; @@ -102,11 +93,23 @@ const languageOptions = Object.entries(AI_GENERATION_LANGUAGES) as [ string, ][]; -const visibilityOptions: Array<{ label: string; value: boolean }> = [ +const languageSelectOptions: SelectOption[] = + languageOptions.map(([code], index) => ({ + label: getAiGenerationLanguageName(code), + value: code, + separatorBefore: index === 1, + })); + +const visibilityOptions: SelectOption[] = [ { label: "Anyone with the link", value: true }, { label: "Private", value: false }, ]; +const speedOptions: SelectOption[] = PLAYBACK_SPEEDS.map((speed) => ({ + label: `${speed}x`, + value: speed, +})); + const mergeSettings = ( settings?: OrganizationSettings | null, ): OrganizationSettings => ({ @@ -123,12 +126,12 @@ const CapSettingsCard = () => { organizationSettings, activeOrganization, instanceVideoDefaultPublic, + setUpgradeModalOpen, } = useDashboardContext(); const router = useRouter(); const initialSettings = mergeSettings(organizationSettings); const [settings, setSettings] = useState(initialSettings); - const [showLanguageMenu, setShowLanguageMenu] = useState(false); const hasDefaultVideoPassword = Boolean( activeOrganization?.organization.hasDefaultVideoPassword, ); @@ -140,7 +143,6 @@ const CapSettingsCard = () => { const [defaultPassword, setDefaultPassword] = useState(""); const lastSavedSettings = useRef(initialSettings); - const languageMenuRef = useRef(null); const debouncedUpdateSettings = useDebounce(settings, 1000); const selectedLanguage = @@ -158,20 +160,6 @@ const CapSettingsCard = () => { setDefaultPassword(""); }, [hasDefaultVideoPassword]); - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - languageMenuRef.current && - !languageMenuRef.current.contains(event.target as Node) - ) { - setShowLanguageMenu(false); - } - }; - - document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); - }, []); - useEffect(() => { if ( debouncedUpdateSettings && @@ -221,26 +209,25 @@ const CapSettingsCard = () => { `Default playback speed set to ${ debouncedUpdateSettings.defaultPlaybackSpeed ?? DEFAULT_PLAYBACK_SPEED - }×`, + }x`, ); return; } - const option = options.find((opt) => opt.value === changedKey); if (changedKey === "hideShareableLinkCapLogo") { toast.success( debouncedUpdateSettings[changedKey] ? "Cap logo hidden" : "Cap logo shown", ); - } else { - const isDisabled = Boolean(debouncedUpdateSettings[changedKey]); - const action = isDisabled ? "disabled" : "enabled"; - const label = option?.label.split(" ")[1] || changedKey; - toast.success( - `${label.charAt(0).toUpperCase()}${label.slice(1)} ${action}`, - ); + return; } + + const option = options.find((opt) => opt.value === changedKey); + const action = debouncedUpdateSettings[changedKey] + ? "disabled" + : "enabled"; + toast.success(`${option?.label ?? changedKey} ${action}`); }); lastSavedSettings.current = debouncedUpdateSettings; @@ -356,263 +343,184 @@ const CapSettingsCard = () => { return; } - setShowLanguageMenu(false); setSettings((prev) => ({ ...prev, aiGenerationLanguage: language, })); }; + const requireProInterceptor = user.isPro + ? undefined + : () => setUpgradeModalOpen(true); + return ( - - - Cap Settings - - Enable or disable specific settings for your organization. These - settings will be applied as defaults for new caps. - - - -
- {options.map((option) => ( -
-
-
-

{option.label}

- {option.pro && ( -

- Pro -

- )} -
-

{option.description}

-
- +
+

Sharing

+
+ + } + /> +
+ } - onCheckedChange={() => { - handleToggle(option.value); - }} - checked={!settings?.[option.value]} /> -
- ))} -
- -
-

Default sharing

- -
-
-
- -

Who can view new caps

-
-

- Applies to new caps. Sharing can still be changed on each cap. -

-
-
- {visibilityOptions.map((option) => ( - - ))} -
-
- -
-
-
-
- -

- Password protect new caps -

-
-

- New caps get this password automatically. Viewers need it to - open the link. -

-
- -
- - {defaultPasswordEnabled && - (hasDefaultVideoPassword && !isChangingDefaultPassword ? ( -
-

- A default password is set. It cannot be shown again. -

-
- - + {defaultPasswordEnabled && + (hasDefaultVideoPassword && !isChangingDefaultPassword ? ( +
+

+ A default password is set. +

+
+ + +
-
- ) : ( -
- setDefaultPassword(e.target.value)} - /> -
- - {hasDefaultVideoPassword && ( + ) : ( +
+ setDefaultPassword(e.target.value)} + /> +
- )} + {hasDefaultVideoPassword && ( + + )} +
-
- ))} -
-
- -
-
-
- -

Default playback speed

+ ))}
-

- The speed caps start playing at on shareable links. Viewers can - still change it. -

-
-
- {PLAYBACK_SPEEDS.map((speed) => ( - - ))}
-
- -
-
-
-

AI generation language

-

- Pro -

-
-

- Set the language used for transcripts, titles, summaries, and - chapters. -

+
+ +
+

Playback

+
+ + } + /> + + } + />
-
- - {showLanguageMenu && ( -
- {languageOptions.map(([code, name], index) => ( -
- {index === 1 && ( -
- )} - -
- ))} -
- )} +
+ +
+

Share page

+
+ {options.map((option) => ( + { + if (option.pro && !user.isPro) { + setUpgradeModalOpen(true); + return; + } + + handleToggle(option.value); + }} + checked={!settings?.[option.value]} + /> + } + /> + ))}
-
- + +
); }; diff --git a/apps/web/app/(org)/dashboard/settings/organization/components/SettingsRows.tsx b/apps/web/app/(org)/dashboard/settings/organization/components/SettingsRows.tsx new file mode 100644 index 0000000000..73c938741d --- /dev/null +++ b/apps/web/app/(org)/dashboard/settings/organization/components/SettingsRows.tsx @@ -0,0 +1,125 @@ +"use client"; + +import clsx from "clsx"; +import { Check, ChevronDown } from "lucide-react"; +import { type ReactNode, useEffect, useRef, useState } from "react"; + +export type SelectOption = { + label: string; + value: T; + separatorBefore?: boolean; +}; + +export const ProChip = () => ( + + Pro + +); + +export function SettingSelect({ + value, + options: selectOptions, + onChange, + ariaLabel, + onInterceptOpen, +}: { + value: T; + options: SelectOption[]; + onChange: (value: T) => void; + ariaLabel: string; + onInterceptOpen?: () => void; +}) { + const [open, setOpen] = useState(false); + const containerRef = useRef(null); + + useEffect(() => { + if (!open) return; + + const handleClickOutside = (event: MouseEvent) => { + if ( + containerRef.current && + !containerRef.current.contains(event.target as Node) + ) { + setOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [open]); + + const selected = selectOptions.find((option) => option.value === value); + + return ( +
+ + {open && ( +
+ {selectOptions.map((option) => ( +
+ {option.separatorBefore && ( +
+ )} + +
+ ))} +
+ )} +
+ ); +} + +export const SettingRow = ({ + label, + description, + pro, + control, +}: { + label: string; + description: string; + pro?: boolean; + control: ReactNode; +}) => ( +
+
+
+

{label}

+ {pro && } +
+

{description}

+
+
{control}
+
+); From 33899f5516e88fccc81f08a7c71e8956dda03ee3 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:57:08 +0100 Subject: [PATCH 09/11] feat(web): restyle organization settings tab into sectioned rows --- .../settings/organization/GeneralPage.tsx | 35 +++++-- .../organization/_components/SettingsNav.tsx | 2 +- .../components/AccessEmailDomain.tsx | 74 +++++++-------- .../organization/components/CustomDomain.tsx | 94 +++++++------------ .../organization/components/DeleteOrg.tsx | 51 +++++----- .../organization/components/OrgName.tsx | 73 +++++++------- .../components/OrganizationDetailsCard.tsx | 29 ------ .../components/OrganizationIcon.tsx | 53 +++++------ .../components/ShareableLinkIcon.tsx | 93 +++++++++--------- 9 files changed, 237 insertions(+), 267 deletions(-) delete mode 100644 apps/web/app/(org)/dashboard/settings/organization/components/OrganizationDetailsCard.tsx diff --git a/apps/web/app/(org)/dashboard/settings/organization/GeneralPage.tsx b/apps/web/app/(org)/dashboard/settings/organization/GeneralPage.tsx index e6eb7fc73f..2a7eaf19f7 100644 --- a/apps/web/app/(org)/dashboard/settings/organization/GeneralPage.tsx +++ b/apps/web/app/(org)/dashboard/settings/organization/GeneralPage.tsx @@ -1,15 +1,38 @@ "use client"; +import AccessEmailDomain from "./components/AccessEmailDomain"; +import { CustomDomain } from "./components/CustomDomain"; import DeleteOrg from "./components/DeleteOrg"; -import { OrganizationDetailsCard } from "./components/OrganizationDetailsCard"; +import { OrganizationIcon } from "./components/OrganizationIcon"; +import OrgName from "./components/OrgName"; +import { ShareableLinkIcon } from "./components/ShareableLinkIcon"; export function GeneralPage() { return ( -
-
- -
- +
+
+

Organization

+
+ + + +
+
+ +
+

Access

+
+ + +
+
+ +
+

Danger zone

+
+ +
+
); } diff --git a/apps/web/app/(org)/dashboard/settings/organization/_components/SettingsNav.tsx b/apps/web/app/(org)/dashboard/settings/organization/_components/SettingsNav.tsx index f346434782..74367a4007 100644 --- a/apps/web/app/(org)/dashboard/settings/organization/_components/SettingsNav.tsx +++ b/apps/web/app/(org)/dashboard/settings/organization/_components/SettingsNav.tsx @@ -9,7 +9,7 @@ import { usePathname } from "next/navigation"; export function SettingsNav() { const pathname = usePathname(); const tabs = [ - { label: "General", href: "/dashboard/settings/organization" }, + { label: "Settings", href: "/dashboard/settings/organization" }, { label: "Preferences", href: "/dashboard/settings/organization/preferences", diff --git a/apps/web/app/(org)/dashboard/settings/organization/components/AccessEmailDomain.tsx b/apps/web/app/(org)/dashboard/settings/organization/components/AccessEmailDomain.tsx index d12feffd8e..99c1c1677c 100644 --- a/apps/web/app/(org)/dashboard/settings/organization/components/AccessEmailDomain.tsx +++ b/apps/web/app/(org)/dashboard/settings/organization/components/AccessEmailDomain.tsx @@ -1,10 +1,11 @@ -import { Button, Label } from "@cap/ui"; +import { Button } from "@cap/ui"; import type { Organisation } from "@cap/web-domain"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { toast } from "sonner"; import { updateOrganizationDetails } from "@/actions/organization/update-details"; import { useDashboardContext } from "../../../Contexts"; +import { SettingRow } from "./SettingsRows"; export const AccessEmailDomain = () => { const { activeOrganization } = useDashboardContext(); @@ -33,46 +34,18 @@ export const AccessEmailDomain = () => { }; return ( -
-
- -

- Restrict who can access public "anyone with the link" videos. Add - email domains (e.g.{" "} - - company.com - - ) or specific email addresses (e.g.{" "} - - larry@google.com - - ), separated by commas. -

-

- Members of your organization and spaces can always access videos - shared with them, regardless of this setting.{" "} - - Leave blank to allow anyone with the link. - -

-
-
-