@powerhousedao/vetra-builder-package 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -68,31 +68,31 @@ export class BuilderAccountHandlers {
68
68
  }
69
69
  // Profile operations
70
70
  async handleSetLogo(documentId, action, state) {
71
- await this.dbHelpers.ensureBuilderAccount(documentId);
71
+ await this.dbHelpers.ensureBuilderAccountExistsAndIsNotdeleted(documentId);
72
72
  await this.dbHelpers.updateBuilderAccount(documentId, {
73
73
  profile_logo: action.input.logoUrl,
74
74
  });
75
75
  }
76
76
  async handleSetProfileName(documentId, action, state) {
77
- await this.dbHelpers.ensureBuilderAccount(documentId);
77
+ await this.dbHelpers.ensureBuilderAccountExistsAndIsNotdeleted(documentId);
78
78
  await this.dbHelpers.updateBuilderAccount(documentId, {
79
79
  profile_name: action.input.name,
80
80
  });
81
81
  }
82
82
  async handleSetSlug(documentId, action, state) {
83
- await this.dbHelpers.ensureBuilderAccount(documentId);
83
+ await this.dbHelpers.ensureBuilderAccountExistsAndIsNotdeleted(documentId);
84
84
  await this.dbHelpers.updateBuilderAccount(documentId, {
85
85
  profile_slug: action.input.slug,
86
86
  });
87
87
  }
88
88
  async handleSetProfileDescription(documentId, action, state) {
89
- await this.dbHelpers.ensureBuilderAccount(documentId);
89
+ await this.dbHelpers.ensureBuilderAccountExistsAndIsNotdeleted(documentId);
90
90
  await this.dbHelpers.updateBuilderAccount(documentId, {
91
91
  profile_description: action.input.description,
92
92
  });
93
93
  }
94
94
  async handleUpdateSocials(documentId, action, state) {
95
- await this.dbHelpers.ensureBuilderAccount(documentId);
95
+ await this.dbHelpers.ensureBuilderAccountExistsAndIsNotdeleted(documentId);
96
96
  await this.dbHelpers.updateBuilderAccount(documentId, {
97
97
  profile_socials_x: action.input.x,
98
98
  profile_socials_github: action.input.github,
@@ -101,7 +101,7 @@ export class BuilderAccountHandlers {
101
101
  }
102
102
  // Members operations
103
103
  async handleAddMember(documentId, action, state) {
104
- await this.dbHelpers.ensureBuilderAccount(documentId);
104
+ await this.dbHelpers.ensureBuilderAccountExistsAndIsNotdeleted(documentId);
105
105
  if (!action.input.ethAddress)
106
106
  return;
107
107
  const memberExists = await this.dbHelpers.memberExists(documentId, action.input.ethAddress);
@@ -128,7 +128,7 @@ export class BuilderAccountHandlers {
128
128
  }
129
129
  // Spaces operations
130
130
  async handleAddSpace(documentId, action, state) {
131
- await this.dbHelpers.ensureBuilderAccount(documentId);
131
+ await this.dbHelpers.ensureBuilderAccountExistsAndIsNotdeleted(documentId);
132
132
  const spaceId = toPascalCase(action.input.title);
133
133
  await this.db
134
134
  .insertInto("builder_spaces")
@@ -7,10 +7,15 @@ export declare class DatabaseHelpers {
7
7
  * Ensures a package exists in the database, creating it if it doesn't
8
8
  */
9
9
  ensurePackageExists(documentId: string, driveId: string): Promise<void>;
10
+ /**
11
+ * Check if a document ID is marked as deleted
12
+ */
13
+ private isDocumentDeleted;
10
14
  /**
11
15
  * Ensures a builder account exists in the database, creating it if it doesn't
16
+ * Throws an error if the document was previously deleted
12
17
  */
13
- ensureBuilderAccount(documentId: string): Promise<void>;
18
+ ensureBuilderAccountExistsAndIsNotdeleted(documentId: string): Promise<void>;
14
19
  /**
15
20
  * Updates a package with the provided data
16
21
  */
@@ -25,10 +25,33 @@ export class DatabaseHelpers {
25
25
  .execute();
26
26
  }
27
27
  }
28
+ /**
29
+ * Check if a document ID is marked as deleted
30
+ */
31
+ async isDocumentDeleted(documentId) {
32
+ try {
33
+ const result = await this.db
34
+ .selectFrom("deleted_files")
35
+ .select("id")
36
+ .where("document_id", "=", documentId)
37
+ .executeTakeFirst();
38
+ return !!result;
39
+ }
40
+ catch (error) {
41
+ console.error("Error checking if document is deleted:", error);
42
+ return false;
43
+ }
44
+ }
28
45
  /**
29
46
  * Ensures a builder account exists in the database, creating it if it doesn't
47
+ * Throws an error if the document was previously deleted
30
48
  */
31
- async ensureBuilderAccount(documentId) {
49
+ async ensureBuilderAccountExistsAndIsNotdeleted(documentId) {
50
+ // Check if the document was deleted
51
+ const isDeleted = await this.isDocumentDeleted(documentId);
52
+ if (isDeleted) {
53
+ throw new Error(`Builder account with document ID ${documentId} was previously deleted and cannot be accessed`);
54
+ }
32
55
  const existing = await this.db
33
56
  .selectFrom("builder_accounts")
34
57
  .select("id")
@@ -0,0 +1,21 @@
1
+ import type { Action } from "document-model";
2
+ import type { DB } from "./schema.js";
3
+ import type { IRelationalDb } from "document-drive";
4
+ export type DocumentDriveAction = Action & {
5
+ type: "DELETE_NODE" | "ADD_FILE" | "ADD_FOLDER" | "UPDATE_FILE" | "UPDATE_NODE" | "COPY_NODE" | "MOVE_NODE";
6
+ input: any;
7
+ };
8
+ export declare class DocumentDriveHandlers {
9
+ private db;
10
+ constructor(db: IRelationalDb<DB>);
11
+ /**
12
+ * Check if a document ID is marked as deleted
13
+ */
14
+ isDocumentDeleted(documentId: string): Promise<boolean>;
15
+ /**
16
+ * Get all deleted document IDs for a specific drive
17
+ */
18
+ getDeletedDocumentsInDrive(driveId: string): Promise<string[]>;
19
+ handleDocumentDriveOperation(documentId: string, action: DocumentDriveAction, driveId?: string): Promise<void>;
20
+ private handleDeleteNode;
21
+ }
@@ -0,0 +1,77 @@
1
+ export class DocumentDriveHandlers {
2
+ db;
3
+ constructor(db) {
4
+ this.db = db;
5
+ console.log("DocumentDriveHandlers constructor");
6
+ }
7
+ /**
8
+ * Check if a document ID is marked as deleted
9
+ */
10
+ async isDocumentDeleted(documentId) {
11
+ try {
12
+ const result = await this.db
13
+ .selectFrom("deleted_files")
14
+ .select("id")
15
+ .where("document_id", "=", documentId)
16
+ .executeTakeFirst();
17
+ return !!result;
18
+ }
19
+ catch (error) {
20
+ console.error("Error checking if document is deleted:", error);
21
+ return false;
22
+ }
23
+ }
24
+ /**
25
+ * Get all deleted document IDs for a specific drive
26
+ */
27
+ async getDeletedDocumentsInDrive(driveId) {
28
+ try {
29
+ const results = await this.db
30
+ .selectFrom("deleted_files")
31
+ .select("document_id")
32
+ .where("drive_id", "=", driveId)
33
+ .execute();
34
+ return results.map((r) => r.document_id);
35
+ }
36
+ catch (error) {
37
+ console.error("Error getting deleted documents in drive:", error);
38
+ return [];
39
+ }
40
+ }
41
+ async handleDocumentDriveOperation(documentId, action, driveId) {
42
+ console.log("Handling document drive operation:", action.type);
43
+ switch (action.type) {
44
+ case "DELETE_NODE":
45
+ console.log("Deleting node:", action.input.nodeId);
46
+ await this.handleDeleteNode(documentId, action, driveId);
47
+ break;
48
+ // Add other document-drive operations as needed
49
+ // case "ADD_FILE":
50
+ // await this.handleAddFile(documentId, action, driveId);
51
+ // break;
52
+ // case "UPDATE_FILE":
53
+ // await this.handleUpdateFile(documentId, action, driveId);
54
+ // break;
55
+ }
56
+ }
57
+ async handleDeleteNode(documentId, action, driveId) {
58
+ try {
59
+ const { nodeId } = action.input;
60
+ // Store the deleted file/document ID in the deleted_files table
61
+ // documentId is the drive ID, nodeId is the specific file/node being deleted
62
+ await this.db
63
+ .insertInto("deleted_files")
64
+ .values({
65
+ id: `${documentId}-${nodeId}`, // Unique ID combining drive and node
66
+ document_id: nodeId, // The specific document/file that was deleted
67
+ drive_id: documentId, // The drive containing the deleted file
68
+ deleted_at: new Date(),
69
+ })
70
+ .onConflict((oc) => oc.column("id").doNothing())
71
+ .execute();
72
+ }
73
+ catch (error) {
74
+ console.error("Error handling delete node:", error);
75
+ }
76
+ }
77
+ }
@@ -8,7 +8,10 @@ export const vetraReadModelProcessorFactory = (module) => async (driveHeader) =>
8
8
  const filter = {
9
9
  branch: ["main"],
10
10
  documentId: ["*"],
11
- documentType: ["powerhouse/vetra/builder-account"],
11
+ documentType: [
12
+ "powerhouse/vetra/builder-account",
13
+ "powerhouse/document-drive",
14
+ ],
12
15
  scope: ["global"],
13
16
  };
14
17
  // Create the processor
@@ -5,7 +5,8 @@ export declare class VetraReadModelProcessor extends RelationalDbProcessor<DB> {
5
5
  static getNamespace(driveId: string): string;
6
6
  initAndUpgrade(): Promise<void>;
7
7
  onStrands(strands: InternalTransmitterUpdate[]): Promise<void>;
8
- private handleOperation;
8
+ private handleDocumentDriveOperation;
9
+ private handlePackageOperation;
9
10
  private handleSetLogo;
10
11
  private handleSetProfileName;
11
12
  private handleSetSlug;
@@ -18,11 +18,32 @@ export class VetraReadModelProcessor extends RelationalDbProcessor {
18
18
  continue;
19
19
  }
20
20
  for (const operation of strand.operations) {
21
- await this.handleOperation(strand.documentId, operation.action, operation.state);
21
+ if (strand.documentType.includes("powerhouse/document-drive")) {
22
+ await this.handleDocumentDriveOperation(strand.documentId, strand.driveId, operation.action, operation.state);
23
+ }
24
+ else {
25
+ await this.handlePackageOperation(strand.documentId, operation.action, operation.state);
26
+ }
22
27
  }
23
28
  }
24
29
  }
25
- async handleOperation(documentId, action, state) {
30
+ async handleDocumentDriveOperation(documentId, driveId = "", action, state) {
31
+ switch (action.type) {
32
+ case "DELETE_NODE":
33
+ await this.relationalDb
34
+ .insertInto("deleted_files")
35
+ .values({
36
+ id: documentId + "-" + action.input.id,
37
+ document_id: action.input.id,
38
+ drive_id: driveId,
39
+ deleted_at: new Date(),
40
+ })
41
+ .onConflict((oc) => oc.column("id").doNothing())
42
+ .execute();
43
+ break;
44
+ }
45
+ }
46
+ async handlePackageOperation(documentId, action, state) {
26
47
  switch (action.type) {
27
48
  // Profile operations
28
49
  case "SET_LOGO":
@@ -70,6 +70,15 @@ export async function up(db) {
70
70
  .addForeignKeyConstraint("builder_package_keywords_package_fk", ["package_id"], "builder_packages", ["id"], (cb) => cb.onDelete("cascade"))
71
71
  .ifNotExists()
72
72
  .execute();
73
+ // Create deleted_files table
74
+ await db.schema
75
+ .createTable("deleted_files")
76
+ .addColumn("id", "varchar(255)", (col) => col.primaryKey())
77
+ .addColumn("document_id", "varchar(255)", (col) => col.notNull())
78
+ .addColumn("drive_id", "varchar(255)", (col) => col.notNull())
79
+ .addColumn("deleted_at", "timestamp", (col) => col.defaultTo("now()").notNull())
80
+ .ifNotExists()
81
+ .execute();
73
82
  // Create indexes for better performance
74
83
  await db.schema
75
84
  .createIndex("idx_builder_accounts_slug")
@@ -119,9 +128,22 @@ export async function up(db) {
119
128
  .column("package_id")
120
129
  .ifNotExists()
121
130
  .execute();
131
+ await db.schema
132
+ .createIndex("idx_deleted_files_document_id")
133
+ .on("deleted_files")
134
+ .column("document_id")
135
+ .ifNotExists()
136
+ .execute();
137
+ await db.schema
138
+ .createIndex("idx_deleted_files_drive_id")
139
+ .on("deleted_files")
140
+ .column("drive_id")
141
+ .ifNotExists()
142
+ .execute();
122
143
  }
123
144
  export async function down(db) {
124
145
  // Drop tables in reverse order due to foreign key constraints
146
+ await db.schema.dropTable("deleted_files").ifExists().execute();
125
147
  await db.schema.dropTable("builder_package_keywords").ifExists().execute();
126
148
  await db.schema.dropTable("builder_packages").ifExists().execute();
127
149
  await db.schema.dropTable("builder_spaces").ifExists().execute();
@@ -50,10 +50,17 @@ export interface BuilderSpaces {
50
50
  title: string;
51
51
  updated_at: Generated<Timestamp>;
52
52
  }
53
+ export interface DeletedFiles {
54
+ deleted_at: Generated<Timestamp>;
55
+ document_id: string;
56
+ drive_id: string;
57
+ id: string;
58
+ }
53
59
  export interface DB {
54
60
  builder_account_members: BuilderAccountMembers;
55
61
  builder_accounts: BuilderAccounts;
56
62
  builder_package_keywords: BuilderPackageKeywords;
57
63
  builder_packages: BuilderPackages;
58
64
  builder_spaces: BuilderSpaces;
65
+ deleted_files: DeletedFiles;
59
66
  }
@@ -10,7 +10,11 @@ export const getResolvers = (subgraph) => {
10
10
  const spaces = await VetraReadModelProcessor.query(driveId, db)
11
11
  .selectFrom("builder_spaces")
12
12
  .selectAll()
13
+ .leftJoin("deleted_files", (join) => join
14
+ .onRef("deleted_files.document_id", "=", "builder_spaces.builder_account_id")
15
+ .on("deleted_files.drive_id", "=", driveId))
13
16
  .where("builder_account_id", "=", parent.id)
17
+ .where("deleted_files.id", "is", null) // Exclude spaces from deleted accounts
14
18
  .orderBy("sort_order", "asc")
15
19
  .execute();
16
20
  return spaces.map((space) => ({
@@ -30,7 +34,11 @@ export const getResolvers = (subgraph) => {
30
34
  const members = await VetraReadModelProcessor.query(driveId, db)
31
35
  .selectFrom("builder_account_members")
32
36
  .selectAll()
37
+ .leftJoin("deleted_files", (join) => join
38
+ .onRef("deleted_files.document_id", "=", "builder_account_members.builder_account_id")
39
+ .on("deleted_files.drive_id", "=", driveId))
33
40
  .where("builder_account_id", "=", parent.id)
41
+ .where("deleted_files.id", "is", null) // Exclude members from deleted accounts
34
42
  .execute();
35
43
  return members.map((member) => ({
36
44
  id: member.id,
@@ -46,7 +54,12 @@ export const getResolvers = (subgraph) => {
46
54
  const packages = await VetraReadModelProcessor.query(driveId, db)
47
55
  .selectFrom("builder_packages")
48
56
  .selectAll()
49
- .where("space_id", "=", parent.id)
57
+ .leftJoin("builder_spaces", (join) => join.onRef("builder_spaces.id", "=", "builder_packages.space_id"))
58
+ .leftJoin("deleted_files", (join) => join
59
+ .onRef("deleted_files.document_id", "=", "builder_spaces.builder_account_id")
60
+ .on("deleted_files.drive_id", "=", driveId))
61
+ .where("builder_packages.space_id", "=", parent.id)
62
+ .where("deleted_files.id", "is", null) // Exclude packages from deleted accounts
50
63
  .orderBy("sort_order", "asc")
51
64
  .execute();
52
65
  return packages.map((pkg) => ({
@@ -74,7 +87,13 @@ export const getResolvers = (subgraph) => {
74
87
  const keywords = await VetraReadModelProcessor.query(driveId, db)
75
88
  .selectFrom("builder_package_keywords")
76
89
  .selectAll()
77
- .where("package_id", "=", parent.id)
90
+ .leftJoin("builder_packages", (join) => join.onRef("builder_packages.id", "=", "builder_package_keywords.package_id"))
91
+ .leftJoin("builder_spaces", (join) => join.onRef("builder_spaces.id", "=", "builder_packages.space_id"))
92
+ .leftJoin("deleted_files", (join) => join
93
+ .onRef("deleted_files.document_id", "=", "builder_spaces.builder_account_id")
94
+ .on("deleted_files.drive_id", "=", driveId))
95
+ .where("builder_package_keywords.package_id", "=", parent.id)
96
+ .where("deleted_files.id", "is", null) // Exclude keywords from deleted accounts
78
97
  .execute();
79
98
  return keywords.map((keyword) => ({
80
99
  id: keyword.id,
@@ -91,7 +110,11 @@ export const getResolvers = (subgraph) => {
91
110
  const sortOrder = args.sortOrder || "asc";
92
111
  let accounts = VetraReadModelProcessor.query(driveId, db)
93
112
  .selectFrom("builder_accounts")
94
- .selectAll();
113
+ .selectAll()
114
+ .leftJoin("deleted_files", (join) => join
115
+ .onRef("deleted_files.document_id", "=", "builder_accounts.id")
116
+ .on("deleted_files.drive_id", "=", driveId))
117
+ .where("deleted_files.id", "is", null); // Exclude deleted documents
95
118
  if (search) {
96
119
  accounts = accounts.where((eb) => {
97
120
  return eb("profile_name", "ilike", `%${search}%`)
@@ -123,7 +146,11 @@ export const getResolvers = (subgraph) => {
123
146
  const account = await VetraReadModelProcessor.query(driveId, db)
124
147
  .selectFrom("builder_accounts")
125
148
  .selectAll()
126
- .where("id", "=", args.id)
149
+ .leftJoin("deleted_files", (join) => join
150
+ .onRef("deleted_files.document_id", "=", "builder_accounts.id")
151
+ .on("deleted_files.drive_id", "=", driveId))
152
+ .where("builder_accounts.id", "=", args.id)
153
+ .where("deleted_files.id", "is", null) // Exclude deleted documents
127
154
  .executeTakeFirst();
128
155
  if (!account) {
129
156
  return null;
@@ -72,6 +72,7 @@ export const schema = gql `
72
72
  fetchAllBuilderAccounts(
73
73
  driveId: String
74
74
  search: String
75
+ sortOrder: String
75
76
  ): [BuilderAccountType!]!
76
77
  fetchBuilderAccount(driveId: String, id: String!): BuilderAccountType
77
78
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@powerhousedao/vetra-builder-package",
3
3
  "description": "",
4
- "version": "0.0.12",
4
+ "version": "0.0.14",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
7
7
  "files": [