Skip to content

Commit e663361

Browse files
committed
store counter and current state in the DB to order and verify events on the server
1 parent f7fea55 commit e663361

File tree

8 files changed

+94
-7
lines changed

8 files changed

+94
-7
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
Warnings:
3+
4+
- Added the required column `counter` to the `SpaceEvent` table without a default value. This is not possible if the table is not empty.
5+
- Added the required column `state` to the `SpaceEvent` table without a default value. This is not possible if the table is not empty.
6+
7+
*/
8+
-- RedefineTables
9+
PRAGMA defer_foreign_keys=ON;
10+
PRAGMA foreign_keys=OFF;
11+
CREATE TABLE "new_SpaceEvent" (
12+
"id" TEXT NOT NULL PRIMARY KEY,
13+
"event" TEXT NOT NULL,
14+
"state" TEXT NOT NULL,
15+
"counter" INTEGER NOT NULL,
16+
"spaceId" TEXT NOT NULL,
17+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
18+
CONSTRAINT "SpaceEvent_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
19+
);
20+
INSERT INTO "new_SpaceEvent" ("event", "id", "spaceId") SELECT "event", "id", "spaceId" FROM "SpaceEvent";
21+
DROP TABLE "SpaceEvent";
22+
ALTER TABLE "new_SpaceEvent" RENAME TO "SpaceEvent";
23+
CREATE UNIQUE INDEX "SpaceEvent_spaceId_counter_key" ON "SpaceEvent"("spaceId", "counter");
24+
PRAGMA foreign_keys=ON;
25+
PRAGMA defer_foreign_keys=OFF;

apps/server/prisma/schema.prisma

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@ datasource db {
1111
}
1212

1313
model SpaceEvent {
14-
id String @id
15-
event String
16-
space Space @relation(fields: [spaceId], references: [id])
17-
spaceId String
14+
id String @id
15+
event String
16+
state String
17+
counter Int
18+
space Space @relation(fields: [spaceId], references: [id])
19+
spaceId String
20+
createdAt DateTime @default(now())
21+
22+
@@unique([spaceId, counter])
1823
}
1924

2025
model Space {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Effect, Exit } from 'effect';
2+
import type { SpaceEvent } from 'graph-framework-space-events';
3+
import { applyEvent } from 'graph-framework-space-events';
4+
import { prisma } from '../prisma.js';
5+
6+
type Params = {
7+
accountId: string;
8+
spaceId: string;
9+
event: SpaceEvent;
10+
};
11+
12+
export async function applySpaceEvent({ accountId, spaceId, event }: Params) {
13+
return await prisma.$transaction(async (transaction) => {
14+
if (event.transaction.type === 'create-space') {
15+
throw new Error('applySpaceEvent does not support create-space events.');
16+
}
17+
18+
// verify that the account is a member of the space
19+
// TODO verify that the account is a admin of the space
20+
await transaction.space.findUniqueOrThrow({
21+
where: { id: spaceId, members: { some: { id: accountId } } },
22+
});
23+
24+
const lastEvent = await transaction.spaceEvent.findFirstOrThrow({
25+
where: { spaceId },
26+
orderBy: { counter: 'desc' },
27+
});
28+
29+
const result = await Effect.runPromiseExit(applyEvent({ event }));
30+
if (Exit.isFailure(result)) {
31+
throw new Error('Invalid event');
32+
}
33+
34+
return await transaction.spaceEvent.create({
35+
data: {
36+
spaceId,
37+
counter: lastEvent.counter + 1,
38+
event: JSON.stringify(event),
39+
id: event.transaction.id,
40+
state: JSON.stringify(result.value),
41+
},
42+
});
43+
});
44+
}

apps/server/src/handlers/createSpace.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { CreateSpaceEvent } from 'graph-framework-space-events';
1+
import { Effect, Exit } from 'effect';
2+
import { type CreateSpaceEvent, applyEvent } from 'graph-framework-space-events';
23
import { prisma } from '../prisma.js';
34

45
type Params = {
@@ -7,10 +8,17 @@ type Params = {
78
};
89

910
export const createSpace = async ({ accountId, event }: Params) => {
11+
const result = await Effect.runPromiseExit(applyEvent({ event }));
12+
if (Exit.isFailure(result)) {
13+
throw new Error('Invalid event');
14+
}
15+
1016
return await prisma.spaceEvent.create({
1117
data: {
1218
event: JSON.stringify(event),
1319
id: event.transaction.id,
20+
counter: 0,
21+
state: JSON.stringify(result.value),
1422
space: {
1523
create: {
1624
id: event.transaction.id,

apps/server/src/handlers/getSpace.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ export const getSpace = async ({ spaceId, accountId }: Params) => {
1616
},
1717
},
1818
include: {
19-
events: true,
19+
events: {
20+
orderBy: {
21+
counter: 'asc',
22+
},
23+
},
2024
},
2125
});
2226
};

packages/graph-framework-space-events/src/apply-event.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { secp256k1 } from '@noble/curves/secp256k1';
22
import { Effect, Schema } from 'effect';
33
import type { ParseError } from 'effect/ParseResult';
44
import { canonicalize, stringToUint8Array } from 'graph-framework-utils';
5-
import { hashEvent } from './hashEvent.js';
5+
import { hashEvent } from './hash-event.js';
66
import {
77
InvalidEventError,
88
SpaceEvent,

packages/graph-framework-space-events/src/hashEvent.ts renamed to packages/graph-framework-space-events/src/hash-event.ts

File renamed without changes.

packages/graph-framework-space-events/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export * from './apply-event.js';
22
export * from './create-invitation.js';
33
export * from './create-space.js';
44
export * from './delete-space.js';
5+
export * from './hash-event.js';
56
export * from './types.js';

0 commit comments

Comments
 (0)