diff --git a/migrations/0010_yummy_runaways.sql b/migrations/0010_yummy_runaways.sql new file mode 100644 index 0000000..af64384 --- /dev/null +++ b/migrations/0010_yummy_runaways.sql @@ -0,0 +1,5 @@ +DROP TABLE "base"."board_columns" CASCADE; +DROP TABLE "base"."boards_views" CASCADE; +DROP TABLE "base"."boards" CASCADE; +DROP TYPE "base"."board_type"; +DROP TYPE "base"."column_status"; \ No newline at end of file diff --git a/migrations/meta/0010_snapshot.json b/migrations/meta/0010_snapshot.json new file mode 100644 index 0000000..5cf4bc2 --- /dev/null +++ b/migrations/meta/0010_snapshot.json @@ -0,0 +1,1209 @@ +{ + "id": "538a6952-f990-41f5-8300-a030d99d738d", + "prevId": "9afd9304-3a2f-40df-986d-b9fcd4f0e596", + "version": "7", + "dialect": "postgresql", + "tables": { + "base.user_activity": { + "name": "user_activity", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_activity_user_id_users_id_fk": { + "name": "user_activity_user_id_users_id_fk", + "tableFrom": "user_activity", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_notifications": { + "name": "user_notifications", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"email\":{\"task_assigned\":true,\"mentions\":true,\"daily_summary\":false},\"push\":{\"task_assigned\":true,\"reminders\":true}}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_notifications_user_id_users_id_fk": { + "name": "user_notifications_user_id_users_id_fk", + "tableFrom": "user_notifications", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_preferences": { + "name": "user_preferences", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'system'" + }, + "timezone": { + "name": "timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "language": { + "name": "language", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'ru'" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_security": { + "name": "user_security", + "schema": "base", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "recovery_email": { + "name": "recovery_email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "is_2fa_enabled": { + "name": "is_2fa_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_secret": { + "name": "two_factor_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_login_at": { + "name": "last_login_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_password_change": { + "name": "last_password_change", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_security_user_id_users_id_fk": { + "name": "user_security_user_id_users_id_fk", + "tableFrom": "user_security", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.users": { + "name": "users", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "headline": { + "name": "headline", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "middle_name": { + "name": "middle_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "vacation_start": { + "name": "vacation_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "vacation_end": { + "name": "vacation_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "vacation_message": { + "name": "vacation_message", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "gender": { + "name": "gender", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'none'" + }, + "pronouns": { + "name": "pronouns", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'none'" + }, + "pronouns_custom": { + "name": "pronouns_custom", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_verified_at": { + "name": "email_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.sessions": { + "name": "sessions", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "browser": { + "name": "browser", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "os": { + "name": "os", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "city": { + "name": "city", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "country_code": { + "name": "country_code", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "is_revoked": { + "name": "is_revoked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.user_identities": { + "name": "user_identities", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_identities_user_id_users_id_fk": { + "name": "user_identities_user_id_users_id_fk", + "tableFrom": "user_identities", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_user_id_idx": { + "name": "provider_user_id_idx", + "nullsNotDistinct": false, + "columns": [ + "provider", + "provider_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.team_members": { + "name": "team_members", + "schema": "base", + "columns": { + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_role", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "member_status", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'inactive'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_status_idx": { + "name": "member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_role_idx": { + "name": "member_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "schemaTo": "base", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_members_team_id_user_id_pk": { + "name": "team_members_team_id_user_id_pk", + "columns": [ + "team_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.teams": { + "name": "teams", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_url": { + "name": "cover_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "team_owner_idx": { + "name": "team_owner_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_deleted_at_idx": { + "name": "team_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "teams_owner_id_users_id_fk": { + "name": "teams_owner_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.project_shares": { + "name": "project_shares", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "token_idx": { + "name": "token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_share_project_id_idx": { + "name": "project_share_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_shares_project_id_projects_id_fk": { + "name": "project_shares_project_id_projects_id_fk", + "tableFrom": "project_shares", + "tableTo": "projects", + "schemaTo": "base", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "project_shares_token_unique": { + "name": "project_shares_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "base.projects": { + "name": "projects", + "schema": "base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "varchar(7)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "project_status", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "task_sequence": { + "name": "task_sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "project_visibility", + "typeSchema": "base", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "project_team_key_idx": { + "name": "project_team_key_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"base\".\"projects\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_team_name_idx": { + "name": "project_team_name_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"base\".\"projects\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_owner_id_idx": { + "name": "project_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_team_id_idx": { + "name": "project_team_id_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_team_id_teams_id_fk": { + "name": "projects_team_id_teams_id_fk", + "tableFrom": "projects", + "tableTo": "teams", + "schemaTo": "base", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "projects_owner_id_users_id_fk": { + "name": "projects_owner_id_users_id_fk", + "tableFrom": "projects", + "tableTo": "users", + "schemaTo": "base", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "base.team_role": { + "name": "team_role", + "schema": "base", + "values": [ + "owner", + "admin", + "lead", + "moderator", + "member", + "viewer" + ] + }, + "base.member_status": { + "name": "member_status", + "schema": "base", + "values": [ + "active", + "banned", + "inactive" + ] + }, + "base.project_status": { + "name": "project_status", + "schema": "base", + "values": [ + "active", + "archived", + "template" + ] + }, + "base.project_visibility": { + "name": "project_visibility", + "schema": "base", + "values": [ + "public", + "private" + ] + } + }, + "schemas": { + "base": "base" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 1118b8c..9b86138 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -71,6 +71,13 @@ "when": 1780853186023, "tag": "0009_true_avengers", "breakpoints": false + }, + { + "idx": 10, + "version": "7", + "when": 1780857935273, + "tag": "0010_yummy_runaways", + "breakpoints": false } ] } \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index 59fe0b8..0f482ee 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -22,7 +22,6 @@ import { CACHE_SERVICE } from '@shared/adapters/cache/constants'; import { ICacheService } from '@shared/adapters/cache/ports'; import { DatabaseHealthService } from '@libs/database'; import { ZodValidationInterceptor } from '@shared/interceptors'; -import { BoardsModule } from '@core/boards'; @Module({ imports: [ @@ -64,7 +63,6 @@ import { BoardsModule } from '@core/boards'; UserModule, TeamsModule, ProjectsModule, - BoardsModule, HealthModule.registerAsync({ inject: [DatabaseHealthService, S3Service, CACHE_SERVICE], useFactory: (db: DatabaseHealthService, s3: S3Service, cache: ICacheService) => { diff --git a/src/boards/application/boards.facade.ts b/src/boards/application/boards.facade.ts deleted file mode 100644 index 86bba8a..0000000 --- a/src/boards/application/boards.facade.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { - CreateBoardUseCase, - DeleteBoardUseCase, - GetBoardQuery, - GetBoardsQuery, - UpdateBoardUseCase, - CreateBoardColumnUseCase, - UpdateBoardColumnUseCase, - DeleteBoardColumnUseCase, - GetBoardColumnsQuery, - GetBoardColumnQuery, - CreateBoardViewUseCase, - UpdateBoardViewUseCase, - DeleteBoardViewUseCase, - GetBoardViewsQuery, - GetBoardViewQuery, -} from './use-cases'; -import { - CreateBoardDto, - CreateBoardColumnDto, - CreateBoardColumnResponse, - CreateBoardViewDto, - CreateBoardViewResponse, - CreateBoardResponse, - BoardColumnsResponse, - BoardListResponse, - BoardViewsResponse, - UpdateBoardColumnDto, - UpdateBoardDto, - UpdateBoardViewDto, -} from './dtos'; -import type { BoardColumn, BoardView, BoardWithRelations } from '@core/boards/domain/entities'; -import type { ActionResponse } from '@shared/dtos'; - -@Injectable() -export class BoardsFacade { - constructor( - private readonly createBoardUC: CreateBoardUseCase, - private readonly updateBoardUC: UpdateBoardUseCase, - private readonly deleteBoardUC: DeleteBoardUseCase, - private readonly getBoardQ: GetBoardQuery, - private readonly getBoardsQ: GetBoardsQuery, - - private readonly createBoardColumnUC: CreateBoardColumnUseCase, - private readonly updateBoardColumnUC: UpdateBoardColumnUseCase, - private readonly deleteBoardColumnUC: DeleteBoardColumnUseCase, - private readonly getBoardColumnsQ: GetBoardColumnsQuery, - private readonly getBoardColumnQ: GetBoardColumnQuery, - - private readonly createBoardViewUC: CreateBoardViewUseCase, - private readonly updateBoardViewUC: UpdateBoardViewUseCase, - private readonly deleteBoardViewUC: DeleteBoardViewUseCase, - private readonly getBoardViewsQ: GetBoardViewsQuery, - private readonly getBoardViewQ: GetBoardViewQuery, - ) {} - - public async create( - projectId: string, - userId: string, - dto: CreateBoardDto, - ): Promise { - return this.createBoardUC.execute(projectId, userId, dto); - } - - public async update( - id: string, - projectId: string, - userId: string, - dto: UpdateBoardDto, - ): Promise { - return this.updateBoardUC.execute(id, projectId, userId, dto); - } - - public async delete(id: string, projectId: string, userId: string): Promise { - return this.deleteBoardUC.execute(id, projectId, userId); - } - - public async getOne( - id: string, - projectId: string, - userId: string, - ): Promise { - return this.getBoardQ.execute(id, projectId, userId); - } - - public async getAll(projectId: string, userId: string): Promise { - return this.getBoardsQ.execute(projectId, userId); - } - - public async createColumn( - boardId: string, - userId: string, - dto: CreateBoardColumnDto, - ): Promise { - return this.createBoardColumnUC.execute(boardId, userId, dto); - } - - public async updateColumn( - id: string, - boardId: string, - userId: string, - dto: UpdateBoardColumnDto, - ): Promise { - return this.updateBoardColumnUC.execute(id, boardId, userId, dto); - } - - public async deleteColumn( - id: string, - boardId: string, - userId: string, - ): Promise { - return this.deleteBoardColumnUC.execute(id, boardId, userId); - } - - public async getColumn( - id: string, - boardId: string, - userId: string, - ): Promise { - return this.getBoardColumnQ.execute(id, boardId, userId); - } - - public async getColumns(boardId: string, userId: string): Promise { - return this.getBoardColumnsQ.execute(boardId, userId); - } - - public async createView( - boardId: string, - userId: string, - dto: CreateBoardViewDto, - ): Promise { - return this.createBoardViewUC.execute(boardId, userId, dto); - } - - public async updateView( - id: string, - boardId: string, - userId: string, - dto: UpdateBoardViewDto, - ): Promise { - return this.updateBoardViewUC.execute(id, boardId, userId, dto); - } - - public async deleteView(id: string, boardId: string, userId: string): Promise { - return this.deleteBoardViewUC.execute(id, boardId, userId); - } - - public async getView(id: string, boardId: string, userId: string): Promise { - return this.getBoardViewQ.execute(id, boardId, userId); - } - - public async getViews(boardId: string, userId: string): Promise { - return this.getBoardViewsQ.execute(boardId, userId); - } -} diff --git a/src/boards/application/controller/boards/controller.ts b/src/boards/application/controller/boards/controller.ts deleted file mode 100644 index 8f450a3..0000000 --- a/src/boards/application/controller/boards/controller.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { ApiBaseController, GetUserId } from '@shared/decorators'; -import { BoardsFacade } from '@core/boards/application/boards.facade'; -import { Body, Delete, Get, Param, Patch, Post } from '@nestjs/common'; -import { CreateBoardDto, UpdateBoardDto } from '@core/boards/application/dtos'; -import { - CreateBoardSwagger, - FindAllBoardsSwagger, - FindOneBoardSwagger, - RemoveBoardSwagger, - UpdateBoardSwagger, -} from './swagger'; - -@ApiBaseController('projects/:projectId/boards', 'Boards', true) -export class BoardsController { - constructor(private readonly facade: BoardsFacade) {} - - @Get() - @FindAllBoardsSwagger() - async findAll(@Param('projectId') projectId: string, @GetUserId() userId: string) { - return this.facade.getAll(projectId, userId); - } - - @Get(':id') - @FindOneBoardSwagger() - async findOne( - @Param('id') id: string, - @Param('projectId') projectId: string, - @GetUserId() userId: string, - ) { - return this.facade.getOne(id, projectId, userId); - } - - @Post() - @CreateBoardSwagger() - async create( - @Param('projectId') projectId: string, - @GetUserId() userId: string, - @Body() dto: CreateBoardDto, - ) { - return this.facade.create(projectId, userId, dto); - } - - @Patch(':id') - @UpdateBoardSwagger() - async update( - @Param('id') id: string, - @Param('projectId') projectId: string, - @GetUserId() userId: string, - @Body() dto: UpdateBoardDto, - ) { - return this.facade.update(id, projectId, userId, dto); - } - - @Delete(':id') - @RemoveBoardSwagger() - async remove( - @Param('id') id: string, - @Param('projectId') projectId: string, - @GetUserId() userId: string, - ) { - return this.facade.delete(id, projectId, userId); - } -} diff --git a/src/boards/application/controller/boards/swagger.ts b/src/boards/application/controller/boards/swagger.ts deleted file mode 100644 index 0396c1f..0000000 --- a/src/boards/application/controller/boards/swagger.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { applyDecorators, SetMetadata } from '@nestjs/common'; -import { ApiBody, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; -import { ApiForbidden, ApiNotFound, ApiUnauthorized, ApiValidationError } from '@shared/error'; -import { ActionResponse } from '@shared/dtos'; -import { ZOD_RESPONSE_TOKEN } from '@shared/interceptors'; -import { - BoardListResponse, - BoardResponse, - CreateBoardDto, - CreateBoardResponse, - UpdateBoardDto, -} from '../../dtos'; - -export const FindAllBoardsSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Получить список досок проекта' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiResponse({ - status: 200, - description: 'Список досок получен', - type: BoardListResponse.Output, - }), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, BoardListResponse), - ); - -export const FindOneBoardSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Получить доску по ID' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'id', description: 'ID доски', type: 'string' }), - ApiResponse({ - status: 200, - description: 'Данные доски получены', - type: BoardResponse.Output, - }), - ApiNotFound('Доска не найдена'), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, BoardResponse), - ); - -export const CreateBoardSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Создать доску в проекте' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiBody({ type: CreateBoardDto.Output }), - ApiResponse({ - status: 201, - description: 'Доска успешно создана', - type: CreateBoardResponse.Output, - }), - ApiValidationError(), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, CreateBoardResponse), - ); - -export const UpdateBoardSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Обновить доску' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'id', description: 'ID доски', type: 'string' }), - ApiBody({ type: UpdateBoardDto.Output }), - ApiResponse({ - status: 200, - description: 'Доска обновлена', - type: ActionResponse.Output, - }), - ApiValidationError(), - ApiNotFound('Доска не найдена'), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, ActionResponse), - ); - -export const RemoveBoardSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Удалить доску' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'id', description: 'ID доски', type: 'string' }), - ApiResponse({ - status: 200, - description: 'Доска удалена', - type: ActionResponse.Output, - }), - ApiNotFound('Доска не найдена'), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, ActionResponse), - ); diff --git a/src/boards/application/controller/columns/controller.ts b/src/boards/application/controller/columns/controller.ts deleted file mode 100644 index 0d54756..0000000 --- a/src/boards/application/controller/columns/controller.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { ApiBaseController, GetUserId } from '@shared/decorators'; -import { BoardsFacade } from '@core/boards/application/boards.facade'; -import { Body, Delete, Get, Param, Patch, Post } from '@nestjs/common'; -import { CreateBoardColumnDto, UpdateBoardColumnDto } from '@core/boards/application/dtos'; -import { - CreateBoardColumnSwagger, - FindAllBoardColumnsSwagger, - FindOneBoardColumnSwagger, - RemoveBoardColumnSwagger, - UpdateBoardColumnSwagger, -} from './swagger'; - -@ApiBaseController('boards/:boardId/columns', 'Board Columns', true) -export class ColumnsController { - constructor(private readonly facade: BoardsFacade) {} - - @Get() - @FindAllBoardColumnsSwagger() - async findAll(@Param('boardId') boardId: string, @GetUserId() userId: string) { - return this.facade.getColumns(boardId, userId); - } - - @Get(':id') - @FindOneBoardColumnSwagger() - async findOne( - @Param('id') id: string, - @Param('boardId') boardId: string, - @GetUserId() userId: string, - ) { - return this.facade.getColumn(id, boardId, userId); - } - - @Post() - @CreateBoardColumnSwagger() - async create( - @Param('boardId') boardId: string, - @GetUserId() userId: string, - @Body() dto: CreateBoardColumnDto, - ) { - return this.facade.createColumn(boardId, userId, dto); - } - - @Patch(':id') - @UpdateBoardColumnSwagger() - async update( - @Param('id') id: string, - @Param('boardId') boardId: string, - @GetUserId() userId: string, - @Body() dto: UpdateBoardColumnDto, - ) { - return this.facade.updateColumn(id, boardId, userId, dto); - } - - @Delete(':id') - @RemoveBoardColumnSwagger() - async remove( - @Param('id') id: string, - @Param('boardId') boardId: string, - @GetUserId() userId: string, - ) { - return this.facade.deleteColumn(id, boardId, userId); - } -} diff --git a/src/boards/application/controller/columns/swagger.ts b/src/boards/application/controller/columns/swagger.ts deleted file mode 100644 index 998b793..0000000 --- a/src/boards/application/controller/columns/swagger.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { applyDecorators, SetMetadata } from '@nestjs/common'; -import { ApiBody, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; -import { ApiForbidden, ApiNotFound, ApiUnauthorized, ApiValidationError } from '@shared/error'; -import { ActionResponse } from '@shared/dtos'; -import { ZOD_RESPONSE_TOKEN } from '@shared/interceptors'; -import { - BoardColumnResponse, - BoardColumnsResponse, - CreateBoardColumnDto, - CreateBoardColumnResponse, - UpdateBoardColumnDto, -} from '../../dtos'; - -export const FindAllBoardColumnsSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Получить список колонок доски' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'boardId', description: 'ID доски', type: 'string' }), - ApiResponse({ - status: 200, - description: 'Список колонок получен', - type: BoardColumnsResponse.Output, - }), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, BoardColumnsResponse), - ); - -export const FindOneBoardColumnSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Получить колонку по ID' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'boardId', description: 'ID доски', type: 'string' }), - ApiParam({ name: 'id', description: 'ID колонки', type: 'string' }), - ApiResponse({ - status: 200, - description: 'Колонка получена', - type: BoardColumnResponse.Output, - }), - ApiNotFound('Колонка не найдена'), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, BoardColumnResponse), - ); - -export const CreateBoardColumnSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Создать колонку в доске' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'boardId', description: 'ID доски', type: 'string' }), - ApiBody({ type: CreateBoardColumnDto.Output }), - ApiResponse({ - status: 201, - description: 'Колонка создана', - type: CreateBoardColumnResponse.Output, - }), - ApiValidationError(), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, CreateBoardColumnResponse), - ); - -export const UpdateBoardColumnSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Обновить колонку' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'boardId', description: 'ID доски', type: 'string' }), - ApiParam({ name: 'id', description: 'ID колонки', type: 'string' }), - ApiBody({ type: UpdateBoardColumnDto.Output }), - ApiResponse({ - status: 200, - description: 'Колонка обновлена', - type: ActionResponse.Output, - }), - ApiValidationError(), - ApiNotFound('Колонка не найдена'), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, ActionResponse), - ); - -export const RemoveBoardColumnSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Удалить колонку' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'boardId', description: 'ID доски', type: 'string' }), - ApiParam({ name: 'id', description: 'ID колонки', type: 'string' }), - ApiResponse({ - status: 200, - description: 'Колонка удалена', - type: ActionResponse.Output, - }), - ApiNotFound('Колонка не найдена'), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, ActionResponse), - ); diff --git a/src/boards/application/controller/index.ts b/src/boards/application/controller/index.ts deleted file mode 100644 index 6e3a48f..0000000 --- a/src/boards/application/controller/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { BoardsController } from './boards/controller'; -export { ColumnsController } from './columns/controller'; -export { ViewsController } from './views/controller'; diff --git a/src/boards/application/controller/views/controller.ts b/src/boards/application/controller/views/controller.ts deleted file mode 100644 index 0c845f3..0000000 --- a/src/boards/application/controller/views/controller.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Body, Delete, Get, Param, Patch, Post } from '@nestjs/common'; -import { ApiBaseController, GetUserId } from '@shared/decorators'; -import { BoardsFacade } from '@core/boards/application/boards.facade'; -import { CreateBoardViewDto, UpdateBoardViewDto } from '@core/boards/application/dtos'; -import { - CreateBoardViewSwagger, - FindAllBoardViewsSwagger, - FindOneBoardViewSwagger, - RemoveBoardViewSwagger, - UpdateBoardViewSwagger, -} from './swagger'; - -@ApiBaseController('boards/:boardId/views', 'Board Views', true) -export class ViewsController { - constructor(private readonly facade: BoardsFacade) {} - - @Get() - @FindAllBoardViewsSwagger() - async findAll(@Param('boardId') boardId: string, @GetUserId() userId: string) { - return this.facade.getViews(boardId, userId); - } - - @Get(':id') - @FindOneBoardViewSwagger() - async findOne( - @Param('id') id: string, - @Param('boardId') boardId: string, - @GetUserId() userId: string, - ) { - return this.facade.getView(id, boardId, userId); - } - - @Post() - @CreateBoardViewSwagger() - async create( - @Param('boardId') boardId: string, - @GetUserId() userId: string, - @Body() dto: CreateBoardViewDto, - ) { - return this.facade.createView(boardId, userId, dto); - } - - @Patch(':id') - @UpdateBoardViewSwagger() - async update( - @Param('id') id: string, - @Param('boardId') boardId: string, - @GetUserId() userId: string, - @Body() dto: UpdateBoardViewDto, - ) { - return this.facade.updateView(id, boardId, userId, dto); - } - - @Delete(':id') - @RemoveBoardViewSwagger() - async remove( - @Param('id') id: string, - @Param('boardId') boardId: string, - @GetUserId() userId: string, - ) { - return this.facade.deleteView(id, boardId, userId); - } -} diff --git a/src/boards/application/controller/views/swagger.ts b/src/boards/application/controller/views/swagger.ts deleted file mode 100644 index 916f506..0000000 --- a/src/boards/application/controller/views/swagger.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { applyDecorators, SetMetadata } from '@nestjs/common'; -import { ApiBody, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; -import { ApiForbidden, ApiNotFound, ApiUnauthorized, ApiValidationError } from '@shared/error'; -import { ActionResponse } from '@shared/dtos'; -import { ZOD_RESPONSE_TOKEN } from '@shared/interceptors'; -import { - BoardViewResponse, - BoardViewsResponse, - CreateBoardViewDto, - CreateBoardViewResponse, - UpdateBoardViewDto, -} from '../../dtos'; - -export const FindAllBoardViewsSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Получить список представлений доски' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'boardId', description: 'ID доски', type: 'string' }), - ApiResponse({ - status: 200, - description: 'Список представлений получен', - type: BoardViewsResponse.Output, - }), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, BoardViewsResponse), - ); - -export const FindOneBoardViewSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Получить представление по ID' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'boardId', description: 'ID доски', type: 'string' }), - ApiParam({ name: 'id', description: 'ID представления', type: 'string' }), - ApiResponse({ - status: 200, - description: 'Представление получено', - type: BoardViewResponse.Output, - }), - ApiNotFound('Представление не найдено'), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, BoardViewResponse), - ); - -export const CreateBoardViewSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Создать представление в доске' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'boardId', description: 'ID доски', type: 'string' }), - ApiBody({ type: CreateBoardViewDto.Output }), - ApiResponse({ - status: 201, - description: 'Представление создано', - type: CreateBoardViewResponse.Output, - }), - ApiValidationError(), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, CreateBoardViewResponse), - ); - -export const UpdateBoardViewSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Обновить представление' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'boardId', description: 'ID доски', type: 'string' }), - ApiParam({ name: 'id', description: 'ID представления', type: 'string' }), - ApiBody({ type: UpdateBoardViewDto.Output }), - ApiResponse({ - status: 200, - description: 'Представление обновлено', - type: ActionResponse.Output, - }), - ApiValidationError(), - ApiNotFound('Представление не найдено'), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, ActionResponse), - ); - -export const RemoveBoardViewSwagger = () => - applyDecorators( - ApiOperation({ summary: 'Удалить представление' }), - ApiParam({ name: 'projectId', description: 'ID проекта', type: 'string' }), - ApiParam({ name: 'boardId', description: 'ID доски', type: 'string' }), - ApiParam({ name: 'id', description: 'ID представления', type: 'string' }), - ApiResponse({ - status: 200, - description: 'Представление удалено', - type: ActionResponse.Output, - }), - ApiNotFound('Представление не найдено'), - ApiUnauthorized(), - ApiForbidden(), - SetMetadata(ZOD_RESPONSE_TOKEN, ActionResponse), - ); diff --git a/src/boards/application/dtos/boards.dto.ts b/src/boards/application/dtos/boards.dto.ts deleted file mode 100644 index bb4a6da..0000000 --- a/src/boards/application/dtos/boards.dto.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { z } from 'zod/v4'; -import { createZodDto } from 'nestjs-zod'; -import { boardTypeEnum, columnStatusEnum } from '@core/boards/infrastructure/persistence/models'; -import { ActionResponseSchema } from '@shared/dtos'; -import { createPaginationSchema } from '@shared/schemas'; - -export const CreateBoardSchema = z.object({ - name: z - .string() - .min(1, 'Название доски не может быть пустым') - .max(100, 'Название доски не должно превышать 100 символов'), - position: z.number().finite().optional(), - settings: z.record(z.string(), z.unknown()).optional(), -}); - -export class CreateBoardDto extends createZodDto(CreateBoardSchema) {} - -const CreateBoardResponseSchema = ActionResponseSchema.extend({ - boardId: z.string().describe('ID созданной доски'), -}); - -export class CreateBoardResponse extends createZodDto(CreateBoardResponseSchema) {} - -export const UpdateBoardSchema = CreateBoardSchema.partial().refine( - (data) => Object.keys(data).length > 0, - { - error: 'Необходимо передать хотя бы одно поле для обновления', - abort: true, - }, -); - -export class UpdateBoardDto extends createZodDto(UpdateBoardSchema) {} - -export const CreateBoardColumnSchema = z.object({ - name: z - .string() - .min(1, 'Название колонки не может быть пустым') - .max(50, 'Название колонки не должно превышать 50 символов'), - position: z.number().finite(), - color: z - .string() - .regex(/^#[A-Fa-f0-9]{6}$/, 'Цвет должен быть в формате HEX (например, #FFFFFF)') - .optional(), -}); - -export class CreateBoardColumnDto extends createZodDto(CreateBoardColumnSchema) {} - -const CreateBoardColumnResponseSchema = ActionResponseSchema.extend({ - columnId: z.string().describe('ID созданной колонки'), -}); - -export class CreateBoardColumnResponse extends createZodDto(CreateBoardColumnResponseSchema) {} - -export const UpdateBoardColumnSchema = CreateBoardColumnSchema.partial().refine( - (data) => Object.keys(data).length > 0, - { - error: 'Необходимо передать хотя бы одно поле для обновления', - abort: true, - }, -); - -export class UpdateBoardColumnDto extends createZodDto(UpdateBoardColumnSchema) {} - -export const CreateBoardViewSchema = z.object({ - type: z.enum(boardTypeEnum.enumValues), - name: z - .string() - .min(1, 'Название представления не может быть пустым') - .max(100, 'Название представления не должно превышать 100 символов'), - settings: z.record(z.string(), z.unknown()).optional(), - position: z.number().finite(), -}); - -export class CreateBoardViewDto extends createZodDto(CreateBoardViewSchema) {} - -const CreateBoardViewResponseSchema = ActionResponseSchema.extend({ - viewId: z.string().describe('ID созданного представления'), -}); - -export class CreateBoardViewResponse extends createZodDto(CreateBoardViewResponseSchema) {} - -export const UpdateBoardViewSchema = CreateBoardViewSchema.partial().refine( - (data) => Object.keys(data).length > 0, - { - error: 'Необходимо передать хотя бы одно поле для обновления', - abort: true, - }, -); - -export class UpdateBoardViewDto extends createZodDto(UpdateBoardViewSchema) {} - -export const BoardColumnResponseSchema = z.object({ - id: z.string().describe('ID колонки'), - boardId: z.string().describe('ID доски'), - name: z.string().describe('Название колонки'), - position: z.number().describe('Позиция колонки'), - status: z.enum(columnStatusEnum.enumValues), - color: z.string().describe('Цвет колонки в HEX'), - createdAt: z - .string() - .refine((val) => !isNaN(Date.parse(val)), { - message: 'Строка не является валидной датой', - }) - .describe('Дата создания'), - updatedAt: z - .string() - .refine((val) => !isNaN(Date.parse(val)), { - message: 'Строка не является валидной датой', - }) - .describe('Дата обновления'), -}); - -export class BoardColumnResponse extends createZodDto(BoardColumnResponseSchema) {} - -export const BoardViewResponseSchema = z.object({ - id: z.string().describe('ID представления'), - boardId: z.string().describe('ID доски'), - type: z.enum(boardTypeEnum.enumValues).describe('Тип представления'), - name: z.string().describe('Название представления'), - settings: z.record(z.string(), z.unknown()).describe('Настройки представления'), - position: z.number().describe('Позиция представления'), - createdAt: z - .string() - .refine((val) => !isNaN(Date.parse(val)), { - message: 'Строка не является валидной датой', - }) - .describe('Дата создания'), - updatedAt: z - .string() - .refine((val) => !isNaN(Date.parse(val)), { - message: 'Строка не является валидной датой', - }) - .describe('Дата обновления'), -}); - -export class BoardViewResponse extends createZodDto(BoardViewResponseSchema) {} - -export const BoardResponseSchema = z.object({ - id: z.string().describe('ID доски'), - name: z.string().describe('Название доски'), - projectId: z.string().describe('ID проекта'), - settings: z.record(z.string(), z.unknown()).describe('Настройки доски'), - position: z.number().describe('Позиция доски'), - ownerId: z.string().nullable().describe('ID владельца доски'), - createdAt: z - .string() - .refine((val) => !isNaN(Date.parse(val)), { - message: 'Строка не является валидной датой', - }) - .describe('Дата создания'), - updatedAt: z - .string() - .refine((val) => !isNaN(Date.parse(val)), { - message: 'Строка не является валидной датой', - }) - .describe('Дата обновления'), - boardColumns: z.array(BoardColumnResponseSchema).describe('Колонки доски'), - boardViews: z.array(BoardViewResponseSchema).describe('Представления доски'), -}); - -export class BoardResponse extends createZodDto(BoardResponseSchema) {} - -export class BoardColumnsResponse extends createZodDto( - createPaginationSchema(BoardColumnResponseSchema), -) {} - -export class BoardViewsResponse extends createZodDto( - createPaginationSchema(BoardViewResponseSchema), -) {} - -export class BoardListResponse extends createZodDto(createPaginationSchema(BoardResponseSchema)) {} diff --git a/src/boards/application/dtos/index.ts b/src/boards/application/dtos/index.ts deleted file mode 100644 index 851839c..0000000 --- a/src/boards/application/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './boards.dto'; diff --git a/src/boards/application/mappers/boards.mapper.ts b/src/boards/application/mappers/boards.mapper.ts deleted file mode 100644 index dd130c3..0000000 --- a/src/boards/application/mappers/boards.mapper.ts +++ /dev/null @@ -1 +0,0 @@ -export class BoardsMapper {} diff --git a/src/boards/application/mappers/index.ts b/src/boards/application/mappers/index.ts deleted file mode 100644 index c89e9d2..0000000 --- a/src/boards/application/mappers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { BoardsMapper } from './boards.mapper'; diff --git a/src/boards/application/use-cases/create-board-column.use-case.ts b/src/boards/application/use-cases/create-board-column.use-case.ts deleted file mode 100644 index 4f38c3c..0000000 --- a/src/boards/application/use-cases/create-board-column.use-case.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import type { CreateBoardColumnDto } from '@core/boards/application/dtos'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; - -@Injectable() -export class CreateBoardColumnUseCase { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(boardId: string, userId: string, dto: CreateBoardColumnDto) { - await this.policyAccess.validateBoardAccess(boardId, userId); - - const created = await this.boardsRepo.createColumn({ boardId, ...dto }); - - return { - success: true, - message: 'Колонка создана', - columnId: created.id, - }; - } -} diff --git a/src/boards/application/use-cases/create-board-view.use-case.ts b/src/boards/application/use-cases/create-board-view.use-case.ts deleted file mode 100644 index 2050709..0000000 --- a/src/boards/application/use-cases/create-board-view.use-case.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import type { CreateBoardViewDto } from '@core/boards/application/dtos'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; - -@Injectable() -export class CreateBoardViewUseCase { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(boardId: string, userId: string, dto: CreateBoardViewDto) { - await this.policyAccess.validateBoardAccess(boardId, userId); - - const created = await this.boardsRepo.createView({ boardId, ...dto }); - - return { - success: true, - message: 'Представление создано', - viewId: created.id, - }; - } -} diff --git a/src/boards/application/use-cases/create-board.use-case.ts b/src/boards/application/use-cases/create-board.use-case.ts deleted file mode 100644 index d3e5a31..0000000 --- a/src/boards/application/use-cases/create-board.use-case.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import type { CreateBoardDto } from '@core/boards/application/dtos'; -import { BoardFactory } from '@core/boards/domain/factories/board.factory'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; - -@Injectable() -export class CreateBoardUseCase { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(projectId: string, userId: string, dto: CreateBoardDto) { - await this.policyAccess.validateProjectAccess(projectId, userId); - - const { board, columns, views } = BoardFactory.createBoard(projectId, userId, dto); - - const created = await this.boardsRepo.create(board, columns, views); - - return { - success: true, - message: 'Доска успешно создана', - boardId: created.id, - }; - } -} diff --git a/src/boards/application/use-cases/delete-board-column.use-case.ts b/src/boards/application/use-cases/delete-board-column.use-case.ts deleted file mode 100644 index 24763aa..0000000 --- a/src/boards/application/use-cases/delete-board-column.use-case.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { HttpStatus, Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; -import { BaseException } from '@shared/error'; - -@Injectable() -export class DeleteBoardColumnUseCase { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(id: string, boardId: string, userId: string) { - await this.policyAccess.validateColumnAccess(id, userId, boardId); - - const result = await this.boardsRepo.removeColumn(id); - - if (!result) { - throw new BaseException( - { - code: 'DELETE_FAILED', - message: 'Не удалось удалить колонку доски', - }, - HttpStatus.SERVICE_UNAVAILABLE, - ); - } - - return { - success: true, - message: `Колонка доски успешно удалена`, - }; - } -} diff --git a/src/boards/application/use-cases/delete-board-view.use-case.ts b/src/boards/application/use-cases/delete-board-view.use-case.ts deleted file mode 100644 index 03db14a..0000000 --- a/src/boards/application/use-cases/delete-board-view.use-case.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { HttpStatus, Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; -import { BaseException } from '@shared/error'; - -@Injectable() -export class DeleteBoardViewUseCase { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(id: string, boardId: string, userId: string) { - await this.policyAccess.validateViewAccess(id, userId, boardId); - - const result = await this.boardsRepo.removeView(id); - - if (!result) { - throw new BaseException( - { - code: 'DELETE_FAILED', - message: 'Не удалось удалить представление доски', - }, - HttpStatus.SERVICE_UNAVAILABLE, - ); - } - - return { - success: true, - message: `Представление доски успешно удалено`, - }; - } -} diff --git a/src/boards/application/use-cases/delete-board.use-case.ts b/src/boards/application/use-cases/delete-board.use-case.ts deleted file mode 100644 index dbaa75b..0000000 --- a/src/boards/application/use-cases/delete-board.use-case.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { HttpStatus, Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; -import { BaseException } from '@shared/error'; - -@Injectable() -export class DeleteBoardUseCase { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(id: string, projectId: string, userId: string) { - await this.policyAccess.validateBoardAccess(id, userId, projectId); - - const result = await this.boardsRepo.remove(id); - - if (!result) { - throw new BaseException( - { - code: 'DELETE_FAILED', - message: 'Не удалось удалить доску', - }, - HttpStatus.SERVICE_UNAVAILABLE, - ); - } - - return { - success: true, - message: `Доска успешно удалена`, - }; - } -} diff --git a/src/boards/application/use-cases/get-board-column.query.ts b/src/boards/application/use-cases/get-board-column.query.ts deleted file mode 100644 index 9d19241..0000000 --- a/src/boards/application/use-cases/get-board-column.query.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { HttpStatus, Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; -import { BaseException } from '@shared/error'; - -@Injectable() -export class GetBoardColumnQuery { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(id: string, boardId: string, userId: string) { - await this.policyAccess.validateColumnAccess(id, userId, boardId); - - const result = this.boardsRepo.findColumnById(id); - - if (!result) { - throw new BaseException( - { code: 'NOT_FOUND', message: 'Не удалось найти колонку' }, - HttpStatus.NOT_FOUND, - ); - } - - return result; - } -} diff --git a/src/boards/application/use-cases/get-board-columns.query.ts b/src/boards/application/use-cases/get-board-columns.query.ts deleted file mode 100644 index f9c3d36..0000000 --- a/src/boards/application/use-cases/get-board-columns.query.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; - -@Injectable() -export class GetBoardColumnsQuery { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(boardId: string, userId: string) { - await this.policyAccess.validateBoardAccess(boardId, userId); - - const items = await this.boardsRepo.findColumns(boardId); - - return { - // TODO: реализовать полноценную пагинацию для колонок доски. - items, - meta: { - total: items.length, - totalPages: items.length ? 1 : 0, - page: 1, - limit: 10, - hasPrevPage: false, - hasNextPage: false, - }, - }; - } -} diff --git a/src/boards/application/use-cases/get-board-view.query.ts b/src/boards/application/use-cases/get-board-view.query.ts deleted file mode 100644 index 437ea13..0000000 --- a/src/boards/application/use-cases/get-board-view.query.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { HttpStatus, Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; -import { BaseException } from '@shared/error'; - -@Injectable() -export class GetBoardViewQuery { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(id: string, boardId: string, userId: string) { - await this.policyAccess.validateViewAccess(id, userId, boardId); - - const result = await this.boardsRepo.findViewById(id); - - if (!result) { - throw new BaseException( - { code: 'NOT_FOUND', message: 'Не удалось найти представление доски' }, - HttpStatus.NOT_FOUND, - ); - } - - return result; - } -} diff --git a/src/boards/application/use-cases/get-board-views.query.ts b/src/boards/application/use-cases/get-board-views.query.ts deleted file mode 100644 index 72e04ed..0000000 --- a/src/boards/application/use-cases/get-board-views.query.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; - -@Injectable() -export class GetBoardViewsQuery { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(boardId: string, userId: string) { - await this.policyAccess.validateBoardAccess(boardId, userId); - - const items = await this.boardsRepo.findViews(boardId); - - return { - // TODO: реализовать полноценную пагинацию для представлений доски. - items, - meta: { - total: items.length, - totalPages: items.length ? 1 : 0, - page: 1, - limit: 10, - hasPrevPage: false, - hasNextPage: false, - }, - }; - } -} diff --git a/src/boards/application/use-cases/get-board.query.ts b/src/boards/application/use-cases/get-board.query.ts deleted file mode 100644 index 777ef6d..0000000 --- a/src/boards/application/use-cases/get-board.query.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { HttpStatus, Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; -import { BaseException } from '@shared/error'; - -@Injectable() -export class GetBoardQuery { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(id: string, projectId: string, userId: string) { - await this.policyAccess.validateBoardAccess(id, userId, projectId); - - const result = await this.boardsRepo.findOne(id); - - if (!result) { - throw new BaseException( - { code: 'NOT_FOUND', message: 'Не удалось найти доску' }, - HttpStatus.NOT_FOUND, - ); - } - - return result; - } -} diff --git a/src/boards/application/use-cases/get-boards.query.ts b/src/boards/application/use-cases/get-boards.query.ts deleted file mode 100644 index caf9303..0000000 --- a/src/boards/application/use-cases/get-boards.query.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; - -@Injectable() -export class GetBoardsQuery { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(projectId: string, userId: string) { - await this.policyAccess.validateProjectAccess(projectId, userId); - - const items = await this.boardsRepo.findAll(projectId); - - return { - // TODO: реализовать полноценную пагинацию для досок проекта. - items, - meta: { - total: items.length, - totalPages: items.length ? 1 : 0, - page: 1, - limit: 10, - hasPrevPage: false, - hasNextPage: false, - }, - }; - } -} diff --git a/src/boards/application/use-cases/index.ts b/src/boards/application/use-cases/index.ts deleted file mode 100644 index 8cf712f..0000000 --- a/src/boards/application/use-cases/index.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { CreateBoardUseCase } from './create-board.use-case'; -import { UpdateBoardUseCase } from './update-board.use-case'; -import { DeleteBoardUseCase } from './delete-board.use-case'; -import { GetBoardQuery } from './get-board.query'; -import { GetBoardsQuery } from './get-boards.query'; -import { CreateBoardColumnUseCase } from './create-board-column.use-case'; -import { UpdateBoardColumnUseCase } from './update-board-column.use-case'; -import { DeleteBoardColumnUseCase } from './delete-board-column.use-case'; -import { GetBoardColumnsQuery } from './get-board-columns.query'; -import { GetBoardColumnQuery } from './get-board-column.query'; -import { CreateBoardViewUseCase } from './create-board-view.use-case'; -import { UpdateBoardViewUseCase } from './update-board-view.use-case'; -import { DeleteBoardViewUseCase } from './delete-board-view.use-case'; -import { GetBoardViewsQuery } from './get-board-views.query'; -import { GetBoardViewQuery } from './get-board-view.query'; - -export * from './create-board.use-case'; -export * from './update-board.use-case'; -export * from './delete-board.use-case'; -export * from './get-board.query'; -export * from './get-boards.query'; -export * from './create-board-column.use-case'; -export * from './update-board-column.use-case'; -export * from './delete-board-column.use-case'; -export * from './get-board-columns.query'; -export * from './get-board-column.query'; -export * from './create-board-view.use-case'; -export * from './update-board-view.use-case'; -export * from './delete-board-view.use-case'; -export * from './get-board-views.query'; -export * from './get-board-view.query'; - -export const BoardUseCases = [ - CreateBoardUseCase, - UpdateBoardUseCase, - DeleteBoardUseCase, - CreateBoardColumnUseCase, - UpdateBoardColumnUseCase, - DeleteBoardColumnUseCase, - CreateBoardViewUseCase, - UpdateBoardViewUseCase, - DeleteBoardViewUseCase, -]; -export const BoardQueries = [ - GetBoardQuery, - GetBoardsQuery, - GetBoardColumnsQuery, - GetBoardColumnQuery, - GetBoardViewsQuery, - GetBoardViewQuery, -]; diff --git a/src/boards/application/use-cases/update-board-column.use-case.ts b/src/boards/application/use-cases/update-board-column.use-case.ts deleted file mode 100644 index bb1edf5..0000000 --- a/src/boards/application/use-cases/update-board-column.use-case.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { HttpStatus, Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import type { UpdateBoardColumnDto } from '@core/boards/application/dtos'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; -import { BaseException } from '@shared/error'; - -@Injectable() -export class UpdateBoardColumnUseCase { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(id: string, boardId: string, userId: string, dto: UpdateBoardColumnDto) { - await this.policyAccess.validateColumnAccess(id, userId, boardId); - - const result = await this.boardsRepo.updateColumn(id, dto); - - if (!result) { - throw new BaseException( - { - code: 'UPDATE_FAILED', - message: - 'Изменения не были применены. Возможно, данные идентичны текущим или колонка недоступна', - }, - HttpStatus.BAD_REQUEST, - ); - } - - return { - success: true, - message: 'Колонка успешно обновлена', - }; - } -} diff --git a/src/boards/application/use-cases/update-board-view.use-case.ts b/src/boards/application/use-cases/update-board-view.use-case.ts deleted file mode 100644 index 5b91e5c..0000000 --- a/src/boards/application/use-cases/update-board-view.use-case.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { HttpStatus, Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import type { UpdateBoardViewDto } from '@core/boards/application/dtos'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; -import { BaseException } from '@shared/error'; - -@Injectable() -export class UpdateBoardViewUseCase { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(id: string, boardId: string, userId: string, dto: UpdateBoardViewDto) { - await this.policyAccess.validateViewAccess(id, userId, boardId); - - const result = await this.boardsRepo.updateView(id, dto); - - if (!result) { - throw new BaseException( - { - code: 'UPDATE_FAILED', - message: - 'Изменения не были применены. Возможно, данные идентичны текущим или представление недоступна', - }, - HttpStatus.BAD_REQUEST, - ); - } - - return { - success: true, - message: 'Представление успешно обновлено', - }; - } -} diff --git a/src/boards/application/use-cases/update-board.use-case.ts b/src/boards/application/use-cases/update-board.use-case.ts deleted file mode 100644 index aa30aa0..0000000 --- a/src/boards/application/use-cases/update-board.use-case.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { HttpStatus, Inject, Injectable } from '@nestjs/common'; -import type { IBoardsRepository } from '@core/boards/domain/repository'; -import type { UpdateBoardDto } from '@core/boards/application/dtos'; -import { BoardAccessPolicy } from '@core/boards/domain/policy'; -import { BaseException } from '@shared/error'; - -@Injectable() -export class UpdateBoardUseCase { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly policyAccess: BoardAccessPolicy, - ) {} - - public async execute(id: string, projectId: string, userId: string, dto: UpdateBoardDto) { - await this.policyAccess.validateBoardAccess(id, userId, projectId); - - const result = await this.boardsRepo.update(id, dto); - - if (!result) { - throw new BaseException( - { - code: 'UPDATE_FAILED', - message: - 'Изменения не были применены. Возможно, данные идентичны текущим или доска недоступна', - }, - HttpStatus.BAD_REQUEST, - ); - } - - return { - success: true, - message: 'Доска успешно обновлена', - }; - } -} diff --git a/src/boards/boards.module.ts b/src/boards/boards.module.ts deleted file mode 100644 index 4cd8363..0000000 --- a/src/boards/boards.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Module } from '@nestjs/common'; -import { ProjectsModule } from '@core/projects'; -import { BoardsRepository } from './infrastructure/persistence/repositories'; -import { BoardsController, ColumnsController, ViewsController } from './application/controller'; -import { BoardsFacade } from './application/boards.facade'; -import { BoardQueries, BoardUseCases } from './application/use-cases'; -import { BoardAccessPolicy } from './domain/policy'; - -const REPOSITORY = { - provide: 'IBoardsRepository', - useClass: BoardsRepository, -}; - -@Module({ - imports: [ProjectsModule], - controllers: [BoardsController, ColumnsController, ViewsController], - providers: [REPOSITORY, BoardAccessPolicy, BoardsFacade, ...BoardUseCases, ...BoardQueries], -}) -export class BoardsModule {} diff --git a/src/boards/domain/entities/boards.domain.ts b/src/boards/domain/entities/boards.domain.ts deleted file mode 100644 index edb7813..0000000 --- a/src/boards/domain/entities/boards.domain.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'; -import { - boards, - boardViews, - boardTypeEnum, - boardColumns, - columnStatusEnum, -} from '@core/boards/infrastructure/persistence/models'; - -export type BoardType = (typeof boardTypeEnum.enumValues)[number]; -export type BoardColumnStatus = (typeof columnStatusEnum.enumValues)[number]; - -export type Board = InferSelectModel; -export type NewBoard = InferInsertModel; - -export type BoardColumn = InferSelectModel; -export type NewBoardColumn = InferInsertModel; - -export type BoardView = InferSelectModel; -export type NewBoardView = InferInsertModel; - -export type BoardWithRelations = Board & { - boardColumns: BoardColumn[]; - boardViews: BoardView[]; -}; diff --git a/src/boards/domain/entities/index.ts b/src/boards/domain/entities/index.ts deleted file mode 100644 index 1da2d74..0000000 --- a/src/boards/domain/entities/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './boards.domain'; diff --git a/src/boards/domain/factories/board.factory.ts b/src/boards/domain/factories/board.factory.ts deleted file mode 100644 index 69db0a0..0000000 --- a/src/boards/domain/factories/board.factory.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { BoardType, NewBoard, NewBoardColumn, NewBoardView } from '@core/boards/domain/entities'; -import { createId } from '@paralleldrive/cuid2'; -import { CreateBoardDto } from '@core/boards/application/dtos'; - -export class BoardFactory { - static createView(props: { - boardId: string; - type: BoardType; - name?: string; - position: number; - settings?: Record; - }): NewBoardView { - return { - id: createId(), - boardId: props.boardId, - type: props.type, - name: props.name ?? this.getDefaultViewName(props.type), - position: props.position, - settings: props.settings ?? this.getDefaultSettings(props.type), - }; - } - - static createBoard( - projectId: string, - ownerId: string, - dto: CreateBoardDto, - ): { board: NewBoard; columns: NewBoardColumn[]; views: NewBoardView[] } { - const boardId = createId(); - const boardPosition = dto.position ?? Date.now(); - const board: NewBoard = { - id: boardId, - name: dto.name, - projectId, - ownerId, - position: boardPosition, - settings: dto.settings ?? {}, - }; - - const defaultViewTypes: BoardType[] = ['kanban', 'calendar', 'gantt_matrix']; - - const views = defaultViewTypes.map((type, index) => - this.createView({ - boardId, - type, - position: (index + 1) * 1000, - }), - ); - - const columns: NewBoardColumn[] = [ - { - id: createId(), - boardId, - name: 'Беклог', - position: 1000, - color: '#bd6f2b', - status: 'backlog', - }, - { - id: createId(), - boardId, - name: 'К выполнению', - position: 2000, - color: '#2d62ae', - status: 'todo', - }, - { - id: createId(), - boardId, - name: 'В работе', - position: 3000, - color: '#c1ab38', - status: 'in_progress', - }, - { - id: createId(), - boardId, - name: 'Готово', - position: 4000, - color: '#22c55e', - status: 'done', - }, - { - id: createId(), - boardId, - name: 'Отменено', - position: 99999, - color: '#78817b', - status: 'canceled', - visibility: false, - }, - ]; - - return { board, columns, views }; - } - - private static getDefaultViewName(type: BoardType): string { - const names: Record = { - kanban: 'Доска', - calendar: 'Календарь', - gantt_matrix: 'Гант', - }; - return names[type]; - } - - private static getDefaultSettings(type: BoardType): Record { - switch (type) { - case 'kanban': - return { mock: 'kanban_mock_setting' }; - case 'calendar': - return { mock: 'calendar_mock_setting' }; - case 'gantt_matrix': - return { mock: 'gantt_mock_setting' }; - default: - const exhaustiveCheck: never = type; - return exhaustiveCheck; - } - } -} diff --git a/src/boards/domain/policy/board-access.policy.ts b/src/boards/domain/policy/board-access.policy.ts deleted file mode 100644 index 415a5d6..0000000 --- a/src/boards/domain/policy/board-access.policy.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { HttpStatus, Inject, Injectable } from '@nestjs/common'; -import { IBoardsRepository } from '@core/boards/domain/repository'; -import { BaseException } from '@shared/error'; -import { ProjectAccessPolicy } from '@core/projects/domain/policy'; - -@Injectable() -export class BoardAccessPolicy { - constructor( - @Inject('IBoardsRepository') - private readonly boardsRepo: IBoardsRepository, - private readonly projectAccessPolicy: ProjectAccessPolicy, - ) {} - - public async validateProjectAccess(projectId: string, userId: string): Promise { - await this.projectAccessPolicy.validateProjectAccessById(projectId, userId, 'viewer'); - - const permissions = true; - if (!permissions) { - throw new BaseException( - { code: 'ACCESS_DENIED', message: 'Недостаточно прав для доступа к проекту' }, - HttpStatus.FORBIDDEN, - ); - } - } - - public async validateBoardAccess( - boardId: string, - userId: string, - expectedProjectId?: string, - ): Promise { - const board = await this.boardsRepo.findBoardById(boardId); - - if (!board || (expectedProjectId && board.projectId !== expectedProjectId)) { - throw new BaseException( - { - code: 'BOARD_NOT_FOUND', - message: 'Доска не найдена', - details: [{ target: 'boardId', value: boardId }], - }, - HttpStatus.NOT_FOUND, - ); - } - - await this.validateProjectAccess(board.projectId, userId); - - const permissions = true; - if (!permissions) { - throw new BaseException( - { code: 'ACCESS_DENIED', message: 'Недостаточно прав для доступа к доске' }, - HttpStatus.FORBIDDEN, - ); - } - } - - public async validateColumnAccess( - columnId: string, - userId: string, - expectedBoardId?: string, - ): Promise { - const column = await this.boardsRepo.findColumnById(columnId); - - if (!column || (expectedBoardId && column.boardId !== expectedBoardId)) { - throw new BaseException( - { - code: 'BOARD_COLUMN_NOT_FOUND', - message: 'Колонка не найдена', - details: [{ target: 'columnId', value: columnId }], - }, - HttpStatus.NOT_FOUND, - ); - } - - await this.validateBoardAccess(column.boardId, userId); - - const permissions = true; - if (!permissions) { - throw new BaseException( - { code: 'ACCESS_DENIED', message: 'Недостаточно прав для доступа к колонке' }, - HttpStatus.FORBIDDEN, - ); - } - } - - public async validateViewAccess( - viewId: string, - userId: string, - expectedBoardId?: string, - ): Promise { - const view = await this.boardsRepo.findViewById(viewId); - - if (!view || (expectedBoardId && view.boardId !== expectedBoardId)) { - throw new BaseException( - { - code: 'BOARD_VIEW_NOT_FOUND', - message: 'Представление не найдено', - details: [{ target: 'viewId', value: viewId }], - }, - HttpStatus.NOT_FOUND, - ); - } - - await this.validateBoardAccess(view.boardId, userId); - - const permissions = true; - if (!permissions) { - throw new BaseException( - { code: 'ACCESS_DENIED', message: 'Недостаточно прав для доступа к представлению' }, - HttpStatus.FORBIDDEN, - ); - } - } -} diff --git a/src/boards/domain/policy/index.ts b/src/boards/domain/policy/index.ts deleted file mode 100644 index 29116c3..0000000 --- a/src/boards/domain/policy/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './board-access.policy'; diff --git a/src/boards/domain/repository/boards.repository.interface.ts b/src/boards/domain/repository/boards.repository.interface.ts deleted file mode 100644 index e495796..0000000 --- a/src/boards/domain/repository/boards.repository.interface.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - Board, - BoardColumn, - BoardView, - BoardWithRelations, - NewBoard, - NewBoardColumn, - NewBoardView, -} from '@core/boards/domain/entities'; - -export interface IBoardsRepository { - findAll(projectId: string): Promise; - findOne(id: string): Promise; - findBoardById(id: string): Promise; - create( - board: NewBoard, - columns: NewBoardColumn[], - views: NewBoardView[], - ): Promise; - update(id: string, data: Partial): Promise; - remove(id: string): Promise; - findColumns(boardId: string): Promise; - findColumnById(id: string): Promise; - createColumn(column: NewBoardColumn): Promise; - updateColumn(id: string, data: Partial): Promise; - removeColumn(id: string): Promise; - findViews(boardId: string): Promise; - findViewById(id: string): Promise; - createView(view: NewBoardView): Promise; - updateView(id: string, data: Partial): Promise; - removeView(id: string): Promise; -} diff --git a/src/boards/domain/repository/index.ts b/src/boards/domain/repository/index.ts deleted file mode 100644 index a28a6ac..0000000 --- a/src/boards/domain/repository/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './boards.repository.interface'; diff --git a/src/boards/index.ts b/src/boards/index.ts deleted file mode 100644 index 0d0f5fb..0000000 --- a/src/boards/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { BoardsModule } from './boards.module'; diff --git a/src/boards/infrastructure/persistence/models/boards.model.ts b/src/boards/infrastructure/persistence/models/boards.model.ts deleted file mode 100644 index a20752a..0000000 --- a/src/boards/infrastructure/persistence/models/boards.model.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { - text, - varchar, - timestamp, - jsonb, - doublePrecision, - uniqueIndex, - boolean, -} from 'drizzle-orm/pg-core'; -import { baseSchema, projects, users } from '@shared/entities'; -import { columnStatusEnum, boardTypeEnum } from './enums'; -import { createId } from '@paralleldrive/cuid2'; - -interface ISettings { - [key: string]: unknown; -} - -export const boards = baseSchema.table( - 'boards', - { - id: text('id') - .primaryKey() - .$defaultFn(() => createId()), - name: varchar('name', { length: 100 }).notNull(), - projectId: text('project_id') - .references(() => projects.id, { onDelete: 'cascade' }) - .notNull(), - settings: jsonb('settings').$type().default({}).notNull(), - position: doublePrecision('position').notNull(), - ownerId: text('owner_id').references(() => users.id, { onDelete: 'set null' }), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - }, - (table) => ({ - projectBoardNameIdx: uniqueIndex('project_board_name_idx').on(table.projectId, table.name), - }), -); - -export const boardColumns = baseSchema.table('board_columns', { - id: text('id') - .primaryKey() - .$defaultFn(() => createId()), - boardId: text('board_id') - .references(() => boards.id, { onDelete: 'cascade' }) - .notNull(), - name: varchar('name', { length: 50 }).notNull(), - status: columnStatusEnum('status').default('backlog').notNull(), - visibility: boolean('visibility').default(true).notNull(), - position: doublePrecision('position').notNull(), - - color: varchar('color', { length: 7 }).default('#64748b').notNull(), - - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), -}); - -export const boardViews = baseSchema.table('boards_views', { - id: text('id') - .primaryKey() - .$defaultFn(() => createId()), - boardId: text('board_id') - .references(() => boards.id, { onDelete: 'cascade' }) - .notNull(), - type: boardTypeEnum('type').default('kanban').notNull(), - name: varchar('name', { length: 100 }).notNull(), - settings: jsonb('settings').$type().default({}).notNull(), - position: doublePrecision('position').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), -}); diff --git a/src/boards/infrastructure/persistence/models/enums.ts b/src/boards/infrastructure/persistence/models/enums.ts deleted file mode 100644 index 7d4a308..0000000 --- a/src/boards/infrastructure/persistence/models/enums.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { baseSchema } from '@shared/entities'; - -export const boardTypeEnum = baseSchema.enum('board_type', ['kanban', 'calendar', 'gantt_matrix']); - -export const columnStatusEnum = baseSchema.enum('column_status', [ - 'backlog', - 'todo', - 'in_progress', - 'done', - 'canceled', -]); diff --git a/src/boards/infrastructure/persistence/models/index.ts b/src/boards/infrastructure/persistence/models/index.ts deleted file mode 100644 index fc4838d..0000000 --- a/src/boards/infrastructure/persistence/models/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './boards.model'; -export * from './enums'; diff --git a/src/boards/infrastructure/persistence/repositories/boards.repository.ts b/src/boards/infrastructure/persistence/repositories/boards.repository.ts deleted file mode 100644 index f64f331..0000000 --- a/src/boards/infrastructure/persistence/repositories/boards.repository.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { IBoardsRepository } from '../../../domain/repository'; -import { - Board, - BoardColumn, - BoardView, - BoardWithRelations, - NewBoard, - NewBoardColumn, - NewBoardView, -} from '@core/boards/domain/entities'; -import { DATABASE_SERVICE, DatabaseService } from '@libs/database'; -import * as schema from '../models'; -import { asc, eq, inArray } from 'drizzle-orm'; - -@Injectable() -export class BoardsRepository implements IBoardsRepository { - constructor( - @Inject(DATABASE_SERVICE) - private readonly db: DatabaseService, - ) {} - - async findAll(projectId: string): Promise { - const boards = await this.db - .select() - .from(schema.boards) - .where(eq(schema.boards.projectId, projectId)) - .orderBy(asc(schema.boards.position)); - - if (boards.length === 0) { - return []; - } - - const boardIds = boards.map((board) => board.id); - const [columns, views] = await Promise.all([ - this.db - .select() - .from(schema.boardColumns) - .where(inArray(schema.boardColumns.boardId, boardIds)) - .orderBy(asc(schema.boardColumns.position)), - this.db - .select() - .from(schema.boardViews) - .where(inArray(schema.boardViews.boardId, boardIds)) - .orderBy(asc(schema.boardViews.position)), - ]); - - const columnsByBoardId = this.groupByBoardId(columns); - const viewsByBoardId = this.groupByBoardId(views); - - return boards.map((board) => ({ - ...board, - boardColumns: columnsByBoardId.get(board.id) ?? [], - boardViews: viewsByBoardId.get(board.id) ?? [], - })); - } - - async findOne(id: string): Promise { - const [board] = await this.db.select().from(schema.boards).where(eq(schema.boards.id, id)); - - if (!board) { - return null; - } - - const [boardColumns, boardViews] = await Promise.all([ - this.db - .select() - .from(schema.boardColumns) - .where(eq(schema.boardColumns.boardId, id)) - .orderBy(asc(schema.boardColumns.position)), - this.db - .select() - .from(schema.boardViews) - .where(eq(schema.boardViews.boardId, id)) - .orderBy(asc(schema.boardViews.position)), - ]); - - return { - ...board, - boardColumns, - boardViews, - }; - } - - async findBoardById(id: string): Promise { - const [board] = await this.db.select().from(schema.boards).where(eq(schema.boards.id, id)); - - return board ?? null; - } - - async create( - board: NewBoard, - columns: NewBoardColumn[], - views: NewBoardView[], - ): Promise { - return await this.db.transaction(async (tx) => { - const [newBoard] = await tx - .insert(schema.boards) - .values({ - id: board.id, - name: board.name, - projectId: board.projectId, - ownerId: board.ownerId, - position: board.position, - settings: board.settings, - }) - .returning(); - - const boardViews = await tx.insert(schema.boardViews).values(views).returning(); - - const boardColumns = await tx.insert(schema.boardColumns).values(columns).returning(); - - return { - ...newBoard, - boardViews, - boardColumns, - }; - }); - } - - async update(id: string, data: Partial): Promise { - const [updated] = await this.db - .update(schema.boards) - .set({ ...data, updatedAt: new Date().toISOString() }) - .where(eq(schema.boards.id, id)) - .returning(); - - if (!updated) { - return null; - } - - const [boardColumns, boardViews] = await Promise.all([ - this.db - .select() - .from(schema.boardColumns) - .where(eq(schema.boardColumns.boardId, id)) - .orderBy(asc(schema.boardColumns.position)), - this.db - .select() - .from(schema.boardViews) - .where(eq(schema.boardViews.boardId, id)) - .orderBy(asc(schema.boardViews.position)), - ]); - - return { - ...updated, - boardColumns, - boardViews, - }; - } - - async remove(id: string): Promise { - const result = await this.db - .delete(schema.boards) - .where(eq(schema.boards.id, id)) - .returning({ id: schema.boards.id }); - - return result.length > 0; - } - - async findColumns(boardId: string): Promise { - return this.db - .select() - .from(schema.boardColumns) - .where(eq(schema.boardColumns.boardId, boardId)) - .orderBy(asc(schema.boardColumns.position)); - } - - async findColumnById(id: string): Promise { - const [column] = await this.db - .select() - .from(schema.boardColumns) - .where(eq(schema.boardColumns.id, id)); - - return column ?? null; - } - - async createColumn(column: NewBoardColumn): Promise { - const [created] = await this.db.insert(schema.boardColumns).values(column).returning(); - - return created; - } - - async updateColumn(id: string, data: Partial): Promise { - const [updated] = await this.db - .update(schema.boardColumns) - .set({ ...data, updatedAt: new Date().toISOString() }) - .where(eq(schema.boardColumns.id, id)) - .returning(); - - return updated ?? null; - } - - async removeColumn(id: string): Promise { - const result = await this.db - .delete(schema.boardColumns) - .where(eq(schema.boardColumns.id, id)) - .returning({ id: schema.boardColumns.id }); - - return result.length > 0; - } - - async findViews(boardId: string): Promise { - return this.db - .select() - .from(schema.boardViews) - .where(eq(schema.boardViews.boardId, boardId)) - .orderBy(asc(schema.boardViews.position)); - } - - async findViewById(id: string): Promise { - const [view] = await this.db - .select() - .from(schema.boardViews) - .where(eq(schema.boardViews.id, id)); - - return view ?? null; - } - - async createView(view: NewBoardView): Promise { - const [created] = await this.db.insert(schema.boardViews).values(view).returning(); - - return created; - } - - async updateView(id: string, data: Partial): Promise { - const [updated] = await this.db - .update(schema.boardViews) - .set({ ...data, updatedAt: new Date().toISOString() }) - .where(eq(schema.boardViews.id, id)) - .returning(); - - return updated ?? null; - } - - async removeView(id: string): Promise { - const result = await this.db - .delete(schema.boardViews) - .where(eq(schema.boardViews.id, id)) - .returning({ id: schema.boardViews.id }); - - return result.length > 0; - } - - private groupByBoardId(items: T[]): Map { - const grouped = new Map(); - - for (const item of items) { - const current = grouped.get(item.boardId); - if (current) { - current.push(item); - } else { - grouped.set(item.boardId, [item]); - } - } - - return grouped; - } -} diff --git a/src/boards/infrastructure/persistence/repositories/index.ts b/src/boards/infrastructure/persistence/repositories/index.ts deleted file mode 100644 index 4b4bb14..0000000 --- a/src/boards/infrastructure/persistence/repositories/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { BoardsRepository } from './boards.repository'; diff --git a/src/shared/entities/index.ts b/src/shared/entities/index.ts index 524e3f8..b50a6a2 100644 --- a/src/shared/entities/index.ts +++ b/src/shared/entities/index.ts @@ -3,4 +3,3 @@ export * from '../../user/infrastructure/persistence/models'; export * from '../../auth/infrastructure/persistence/models'; export * from '../../teams/infrastructure/persistence/models'; export * from '../../projects/infrastructure/persistence/models'; -export * from '../../boards/infrastructure/persistence/models';