diff --git a/bun.lock b/bun.lock
index 89dfd9a..49a6051 100644
--- a/bun.lock
+++ b/bun.lock
@@ -7,13 +7,13 @@
"devDependencies": {
"@playwright/test": "^1.59.1",
"@types/better-sqlite3": "^7.6.13",
- "@types/bun": "^1.3.11",
- "better-sqlite3": "^12.8.0",
- "bun-types": "^1.3.11",
+ "@types/bun": "^1.3.12",
+ "better-sqlite3": "^12.9.0",
+ "bun-types": "^1.3.12",
"drizzle-kit": "^0.31.10",
"oxfmt": "^0.43.0",
"typescript": "^6.0.2",
- "vitest": "^4.1.3",
+ "vitest": "^4.1.4",
},
},
"packages/app": {
diff --git a/drizzle/0004_melted_brother_voodoo.sql b/drizzle/0004_melted_brother_voodoo.sql
new file mode 100644
index 0000000..ec026ce
--- /dev/null
+++ b/drizzle/0004_melted_brother_voodoo.sql
@@ -0,0 +1,22 @@
+CREATE TABLE `entity_labels` (
+ `label_id` text NOT NULL,
+ `entity_id` text NOT NULL,
+ `entity_type` text NOT NULL,
+ PRIMARY KEY(`label_id`, `entity_id`),
+ FOREIGN KEY (`label_id`) REFERENCES `sphere_labels`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE INDEX `idx_entity_labels_entity` ON `entity_labels` (`entity_id`,`entity_type`);--> statement-breakpoint
+CREATE TABLE `sphere_labels` (
+ `id` text PRIMARY KEY NOT NULL,
+ `sphere_id` text NOT NULL,
+ `name` text NOT NULL,
+ `description` text,
+ `color` text NOT NULL,
+ `position` integer DEFAULT 0 NOT NULL,
+ `created_at` text DEFAULT (datetime('now')) NOT NULL,
+ FOREIGN KEY (`sphere_id`) REFERENCES `spheres`(`id`) ON UPDATE no action ON DELETE no action
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `idx_sphere_labels_sphere_name` ON `sphere_labels` (`sphere_id`,`name`);--> statement-breakpoint
+CREATE INDEX `idx_sphere_labels_sphere_position` ON `sphere_labels` (`sphere_id`,`position`);
\ No newline at end of file
diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json
new file mode 100644
index 0000000..a1ac23d
--- /dev/null
+++ b/drizzle/meta/0004_snapshot.json
@@ -0,0 +1,1387 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "36c47020-9349-4833-8827-8c5bde6e1b17",
+ "prevId": "496173cb-98e0-46b7-baaf-47929d4cb526",
+ "tables": {
+ "oauth_sessions": {
+ "name": "oauth_sessions",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "session": {
+ "name": "session",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "oauth_states": {
+ "name": "oauth_states",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "indexer_cursor": {
+ "name": "indexer_cursor",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'jetstream'"
+ },
+ "cursor": {
+ "name": "cursor",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sphere_entry_counter": {
+ "name": "sphere_entry_counter",
+ "columns": {
+ "sphere_id": {
+ "name": "sphere_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_number": {
+ "name": "last_number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sphere_entry_counter_sphere_id_spheres_id_fk": {
+ "name": "sphere_entry_counter_sphere_id_spheres_id_fk",
+ "tableFrom": "sphere_entry_counter",
+ "tableTo": "spheres",
+ "columnsFrom": ["sphere_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "entity_labels": {
+ "name": "entity_labels",
+ "columns": {
+ "label_id": {
+ "name": "label_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "idx_entity_labels_entity": {
+ "name": "idx_entity_labels_entity",
+ "columns": ["entity_id", "entity_type"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "entity_labels_label_id_sphere_labels_id_fk": {
+ "name": "entity_labels_label_id_sphere_labels_id_fk",
+ "tableFrom": "entity_labels",
+ "tableTo": "sphere_labels",
+ "columnsFrom": ["label_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "entity_labels_label_id_entity_id_pk": {
+ "columns": ["label_id", "entity_id"],
+ "name": "entity_labels_label_id_entity_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sphere_labels": {
+ "name": "sphere_labels",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sphere_id": {
+ "name": "sphere_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {
+ "idx_sphere_labels_sphere_name": {
+ "name": "idx_sphere_labels_sphere_name",
+ "columns": ["sphere_id", "name"],
+ "isUnique": true
+ },
+ "idx_sphere_labels_sphere_position": {
+ "name": "idx_sphere_labels_sphere_position",
+ "columns": ["sphere_id", "position"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "sphere_labels_sphere_id_spheres_id_fk": {
+ "name": "sphere_labels_sphere_id_spheres_id_fk",
+ "tableFrom": "sphere_labels",
+ "tableTo": "spheres",
+ "columnsFrom": ["sphere_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sphere_members": {
+ "name": "sphere_members",
+ "columns": {
+ "sphere_id": {
+ "name": "sphere_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "did": {
+ "name": "did",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'member'"
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'invited'"
+ },
+ "invited_by": {
+ "name": "invited_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pds_uri": {
+ "name": "pds_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "approval_pds_uri": {
+ "name": "approval_pds_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {
+ "idx_sphere_members_did": {
+ "name": "idx_sphere_members_did",
+ "columns": ["did"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "sphere_members_sphere_id_spheres_id_fk": {
+ "name": "sphere_members_sphere_id_spheres_id_fk",
+ "tableFrom": "sphere_members",
+ "tableTo": "spheres",
+ "columnsFrom": ["sphere_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "sphere_members_sphere_id_did_pk": {
+ "columns": ["sphere_id", "did"],
+ "name": "sphere_members_sphere_id_did_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sphere_modules": {
+ "name": "sphere_modules",
+ "columns": {
+ "sphere_id": {
+ "name": "sphere_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "module_name": {
+ "name": "module_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled_at": {
+ "name": "enabled_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sphere_modules_sphere_id_spheres_id_fk": {
+ "name": "sphere_modules_sphere_id_spheres_id_fk",
+ "tableFrom": "sphere_modules",
+ "tableTo": "spheres",
+ "columnsFrom": ["sphere_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "sphere_modules_sphere_id_module_name_pk": {
+ "columns": ["sphere_id", "module_name"],
+ "name": "sphere_modules_sphere_id_module_name_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sphere_permissions": {
+ "name": "sphere_permissions",
+ "columns": {
+ "sphere_id": {
+ "name": "sphere_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action_key": {
+ "name": "action_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "min_role": {
+ "name": "min_role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sphere_permissions_sphere_id_spheres_id_fk": {
+ "name": "sphere_permissions_sphere_id_spheres_id_fk",
+ "tableFrom": "sphere_permissions",
+ "tableTo": "spheres",
+ "columnsFrom": ["sphere_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "sphere_permissions_sphere_id_action_key_pk": {
+ "columns": ["sphere_id", "action_key"],
+ "name": "sphere_permissions_sphere_id_action_key_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "spheres": {
+ "name": "spheres",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "handle": {
+ "name": "handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'public'"
+ },
+ "owner_did": {
+ "name": "owner_did",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "pds_uri": {
+ "name": "pds_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {
+ "spheres_handle_unique": {
+ "name": "spheres_handle_unique",
+ "columns": ["handle"],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "feature_request_comment_votes": {
+ "name": "feature_request_comment_votes",
+ "columns": {
+ "comment_id": {
+ "name": "comment_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "author_did": {
+ "name": "author_did",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "pds_uri": {
+ "name": "pds_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {
+ "idx_feature_request_comment_votes_comment": {
+ "name": "idx_feature_request_comment_votes_comment",
+ "columns": ["comment_id"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "feature_request_comment_votes_comment_id_feature_request_comments_id_fk": {
+ "name": "feature_request_comment_votes_comment_id_feature_request_comments_id_fk",
+ "tableFrom": "feature_request_comment_votes",
+ "tableTo": "feature_request_comments",
+ "columnsFrom": ["comment_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "feature_request_comment_votes_comment_id_author_did_pk": {
+ "columns": ["comment_id", "author_did"],
+ "name": "feature_request_comment_votes_comment_id_author_did_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "feature_request_comments": {
+ "name": "feature_request_comments",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "request_id": {
+ "name": "request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "author_did": {
+ "name": "author_did",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "pds_uri": {
+ "name": "pds_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "moderated_by": {
+ "name": "moderated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "idx_feature_request_comments_request": {
+ "name": "idx_feature_request_comments_request",
+ "columns": ["request_id"],
+ "isUnique": false
+ },
+ "idx_feature_request_comments_author_request": {
+ "name": "idx_feature_request_comments_author_request",
+ "columns": ["author_did", "request_id"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "feature_request_comments_request_id_feature_requests_id_fk": {
+ "name": "feature_request_comments_request_id_feature_requests_id_fk",
+ "tableFrom": "feature_request_comments",
+ "tableTo": "feature_requests",
+ "columnsFrom": ["request_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "feature_request_statuses": {
+ "name": "feature_request_statuses",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "request_id": {
+ "name": "request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "author_did": {
+ "name": "author_did",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "pds_uri": {
+ "name": "pds_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "idx_feature_request_statuses_request": {
+ "name": "idx_feature_request_statuses_request",
+ "columns": ["request_id"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "feature_request_statuses_request_id_feature_requests_id_fk": {
+ "name": "feature_request_statuses_request_id_feature_requests_id_fk",
+ "tableFrom": "feature_request_statuses",
+ "tableTo": "feature_requests",
+ "columnsFrom": ["request_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "feature_request_votes": {
+ "name": "feature_request_votes",
+ "columns": {
+ "request_id": {
+ "name": "request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "author_did": {
+ "name": "author_did",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "pds_uri": {
+ "name": "pds_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {
+ "idx_feature_request_votes_request": {
+ "name": "idx_feature_request_votes_request",
+ "columns": ["request_id"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "feature_request_votes_request_id_feature_requests_id_fk": {
+ "name": "feature_request_votes_request_id_feature_requests_id_fk",
+ "tableFrom": "feature_request_votes",
+ "tableTo": "feature_requests",
+ "columnsFrom": ["request_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "feature_request_votes_request_id_author_did_pk": {
+ "columns": ["request_id", "author_did"],
+ "name": "feature_request_votes_request_id_author_did_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "feature_requests": {
+ "name": "feature_requests",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sphere_id": {
+ "name": "sphere_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "number": {
+ "name": "number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "author_did": {
+ "name": "author_did",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "category": {
+ "name": "category",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'general'"
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'requested'"
+ },
+ "duplicate_of_id": {
+ "name": "duplicate_of_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pds_uri": {
+ "name": "pds_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "moderated_by": {
+ "name": "moderated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {
+ "idx_feature_requests_sphere_number": {
+ "name": "idx_feature_requests_sphere_number",
+ "columns": ["sphere_id", "number"],
+ "isUnique": true
+ },
+ "idx_feature_requests_sphere": {
+ "name": "idx_feature_requests_sphere",
+ "columns": ["sphere_id"],
+ "isUnique": false
+ },
+ "idx_feature_requests_status": {
+ "name": "idx_feature_requests_status",
+ "columns": ["status"],
+ "isUnique": false
+ },
+ "idx_feature_requests_category": {
+ "name": "idx_feature_requests_category",
+ "columns": ["category"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "feature_requests_sphere_id_spheres_id_fk": {
+ "name": "feature_requests_sphere_id_spheres_id_fk",
+ "tableFrom": "feature_requests",
+ "tableTo": "spheres",
+ "columnsFrom": ["sphere_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "feed_posts": {
+ "name": "feed_posts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "author_did": {
+ "name": "author_did",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pds_uri": {
+ "name": "pds_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {
+ "idx_feed_posts_parent": {
+ "name": "idx_feed_posts_parent",
+ "columns": ["parent_id"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "kanban_columns": {
+ "name": "kanban_columns",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sphere_id": {
+ "name": "sphere_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {
+ "idx_kanban_columns_sphere_slug": {
+ "name": "idx_kanban_columns_sphere_slug",
+ "columns": ["sphere_id", "slug"],
+ "isUnique": true
+ },
+ "idx_kanban_columns_sphere_position": {
+ "name": "idx_kanban_columns_sphere_position",
+ "columns": ["sphere_id", "position"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "kanban_columns_sphere_id_spheres_id_fk": {
+ "name": "kanban_columns_sphere_id_spheres_id_fk",
+ "tableFrom": "kanban_columns",
+ "tableTo": "spheres",
+ "columnsFrom": ["sphere_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "kanban_task_comments": {
+ "name": "kanban_task_comments",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "task_id": {
+ "name": "task_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "author_did": {
+ "name": "author_did",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "pds_uri": {
+ "name": "pds_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "moderated_by": {
+ "name": "moderated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "idx_kanban_task_comments_task": {
+ "name": "idx_kanban_task_comments_task",
+ "columns": ["task_id"],
+ "isUnique": false
+ },
+ "idx_kanban_task_comments_author_task": {
+ "name": "idx_kanban_task_comments_author_task",
+ "columns": ["author_did", "task_id"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "kanban_task_comments_task_id_kanban_tasks_id_fk": {
+ "name": "kanban_task_comments_task_id_kanban_tasks_id_fk",
+ "tableFrom": "kanban_task_comments",
+ "tableTo": "kanban_tasks",
+ "columnsFrom": ["task_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "kanban_task_status_changes": {
+ "name": "kanban_task_status_changes",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "task_id": {
+ "name": "task_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "author_did": {
+ "name": "author_did",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "pds_uri": {
+ "name": "pds_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "idx_kanban_task_status_changes_task": {
+ "name": "idx_kanban_task_status_changes_task",
+ "columns": ["task_id"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "kanban_task_status_changes_task_id_kanban_tasks_id_fk": {
+ "name": "kanban_task_status_changes_task_id_kanban_tasks_id_fk",
+ "tableFrom": "kanban_task_status_changes",
+ "tableTo": "kanban_tasks",
+ "columnsFrom": ["task_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "kanban_tasks": {
+ "name": "kanban_tasks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sphere_id": {
+ "name": "sphere_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "number": {
+ "name": "number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "author_did": {
+ "name": "author_did",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'backlog'"
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "assignee_did": {
+ "name": "assignee_did",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pds_uri": {
+ "name": "pds_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "moderated_by": {
+ "name": "moderated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(datetime('now'))"
+ }
+ },
+ "indexes": {
+ "idx_kanban_tasks_sphere_number": {
+ "name": "idx_kanban_tasks_sphere_number",
+ "columns": ["sphere_id", "number"],
+ "isUnique": true
+ },
+ "idx_kanban_tasks_sphere": {
+ "name": "idx_kanban_tasks_sphere",
+ "columns": ["sphere_id"],
+ "isUnique": false
+ },
+ "idx_kanban_tasks_status": {
+ "name": "idx_kanban_tasks_status",
+ "columns": ["status"],
+ "isUnique": false
+ },
+ "idx_kanban_tasks_sphere_status_position": {
+ "name": "idx_kanban_tasks_sphere_status_position",
+ "columns": ["sphere_id", "status", "position"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "kanban_tasks_sphere_id_spheres_id_fk": {
+ "name": "kanban_tasks_sphere_id_spheres_id_fk",
+ "tableFrom": "kanban_tasks",
+ "tableTo": "spheres",
+ "columnsFrom": ["sphere_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 495837e..0ee8afd 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -29,6 +29,13 @@
"when": 1775304470263,
"tag": "0003_soft_tigra",
"breakpoints": true
+ },
+ {
+ "idx": 4,
+ "version": "6",
+ "when": 1776203212384,
+ "tag": "0004_melted_brother_voodoo",
+ "breakpoints": true
}
]
}
diff --git a/packages/app/src/api/spheres.ts b/packages/app/src/api/spheres.ts
index 5bf2ed7..bccde26 100644
--- a/packages/app/src/api/spheres.ts
+++ b/packages/app/src/api/spheres.ts
@@ -127,3 +127,56 @@ export function updateMemberRole(handle: string, did: string, role: "admin" | "m
body: JSON.stringify({ role }),
});
}
+
+// ---- Labels ----
+
+export type { LabelData } from "@exosphere/client/api/labels";
+import type { LabelData } from "@exosphere/client/api/labels";
+
+export function getSphereLabels(handle: string) {
+ return apiFetch<{ labels: LabelData[] }>(`/api/spheres/${encodeURIComponent(handle)}/labels`);
+}
+
+export function createSphereLabel(
+ handle: string,
+ body: { name: string; description?: string; color: string },
+) {
+ return apiFetch<{ label: LabelData }>(`/api/spheres/${encodeURIComponent(handle)}/labels`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(body),
+ });
+}
+
+export function updateSphereLabel(
+ handle: string,
+ id: string,
+ body: { name?: string; description?: string | null; color?: string },
+) {
+ return apiFetch<{ label: LabelData }>(
+ `/api/spheres/${encodeURIComponent(handle)}/labels/${encodeURIComponent(id)}`,
+ {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(body),
+ },
+ );
+}
+
+export function deleteSphereLabel(handle: string, id: string) {
+ return apiFetch<{ ok: true }>(
+ `/api/spheres/${encodeURIComponent(handle)}/labels/${encodeURIComponent(id)}`,
+ { method: "DELETE" },
+ );
+}
+
+export function reorderSphereLabels(handle: string, labelIds: string[]) {
+ return apiFetch<{ labels: LabelData[] }>(
+ `/api/spheres/${encodeURIComponent(handle)}/labels/reorder`,
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ labelIds }),
+ },
+ );
+}
diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx
index 75af550..098f850 100644
--- a/packages/app/src/app.tsx
+++ b/packages/app/src/app.tsx
@@ -14,6 +14,7 @@ import { CreateSphere } from "./pages/create-sphere.tsx";
import { SpherePage } from "./pages/sphere.tsx";
import { SphereMembersPage } from "./pages/sphere-members.tsx";
import { SpherePermissionsPage } from "./pages/sphere-permissions.tsx";
+import { SphereLabelsPage } from "./pages/sphere-labels.tsx";
import { Dashboard } from "./pages/dashboard.tsx";
import type { ModuleRoute } from "@exosphere/client/types";
import { feedsModule } from "@exosphere/feeds/client";
@@ -138,6 +139,10 @@ function MainContent({ moduleRoutes }: { moduleRoutes: ModuleRoute[] }) {
path="/s/:sphereHandle/settings/permissions"
component={withSphereLoader(SpherePermissionsPage)}
/>
+
{listError.value}
} + {!data ? ( +Loading...
+ ) : data.labels.length === 0 ? ( +No labels yet.
+ ) : ( +Configure who can perform actions in this sphere.
)} + {canDo("sphere", "manageLabels") && ( + + Labels +Create and manage labels for feature requests and tasks.
+ + )}