Skip to content
This repository has been archived by the owner on Sep 17, 2024. It is now read-only.

Commit

Permalink
feat: Create presence module
Browse files Browse the repository at this point in the history
  • Loading branch information
Blckbrry-Pi committed Mar 28, 2024
1 parent b5c0823 commit 3ce63a1
Show file tree
Hide file tree
Showing 16 changed files with 631 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- CreateTable
CREATE TABLE "Presence" (
"identityId" UUID NOT NULL,
"gameId" UUID NOT NULL,
"message" TEXT,
"publicMeta" JSONB NOT NULL,
"mutualMeta" JSONB NOT NULL,
"expires" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"removedAt" TIMESTAMP(3),

CONSTRAINT "Presence_pkey" PRIMARY KEY ("identityId","gameId")
);
3 changes: 3 additions & 0 deletions modules/presence/db/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
24 changes: 24 additions & 0 deletions modules/presence/db/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Do not modify this `datasource` block
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

// Add your database schema here

model Presence {
identityId String @db.Uuid
gameId String @db.Uuid
message String?
publicMeta Json
mutualMeta Json
expires DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
removedAt DateTime?
@@id([identityId, gameId])
}
12 changes: 12 additions & 0 deletions modules/presence/module.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
scripts:
set: {}
extend: {}

get: {}
get_by_game: {}
get_by_identity: {}

clear: {}
clear_all_game: {}
clear_all_identity: {}
errors: {}
36 changes: 36 additions & 0 deletions modules/presence/scripts/clear.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ScriptContext, RuntimeError } from "../_gen/scripts/clear.ts";

export interface Request {
identityId: string;
gameId: string;

errorIfNotPresent?: boolean;
}

export type Response = Record<string, never>;

export async function run(
ctx: ScriptContext,
req: Request,
): Promise<Response> {
const value = await ctx.db.presence.updateMany({
where: {
identityId: req.identityId,
gameId: req.gameId,
},
data: {
removedAt: new Date().toISOString(),
expires: null,
},
});

if (value.count === 0) {
throw new RuntimeError(
"presence_not_found",
{ cause: `Presence not found for identity ${req.identityId} and game ${req.gameId}` },
);
}

return {};
}

26 changes: 26 additions & 0 deletions modules/presence/scripts/clear_all_game.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ScriptContext } from "../_gen/scripts/clear_all_game.ts";

export interface Request {
gameId: string;
}

export interface Response {
cleared: number;
}

export async function run(
ctx: ScriptContext,
req: Request,
): Promise<Response> {
const { count: cleared } = await ctx.db.presence.updateMany({
where: {
gameId: req.gameId,
},
data: {
removedAt: new Date().toISOString(),
expires: null,
},
});

return { cleared };
}
26 changes: 26 additions & 0 deletions modules/presence/scripts/clear_all_identity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ScriptContext } from "../_gen/scripts/clear_all_identity.ts";

export interface Request {
identityId: string;
}

export interface Response {
cleared: number;
}

export async function run(
ctx: ScriptContext,
req: Request,
): Promise<Response> {
const { count: cleared } = await ctx.db.presence.updateMany({
where: {
identityId: req.identityId,
},
data: {
removedAt: new Date().toISOString(),
expires: null,
},
});

return { cleared };
}
63 changes: 63 additions & 0 deletions modules/presence/scripts/extend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ScriptContext, RuntimeError } from "../_gen/scripts/extend.ts";
import { prismaToOutput } from "../utils/types.ts";
import { Presence } from "../utils/types.ts";

export interface Request {
gameId: string;
identityId: string;

expiresInMs: number | null;
reviveIfExpired: boolean;
}

export interface Response {
presence: Presence
}

export async function run(
ctx: ScriptContext,
req: Request,
): Promise<Response> {
const { presence } = await ctx.db.$transaction(async (db) => {
const value = await ctx.db.presence.findFirst({
where: {
identityId: req.identityId,
gameId: req.gameId,
removedAt: null,
},
});

if (!value) {
throw new RuntimeError(
"presence_not_found",
{ cause: `Presence not found for identity ${req.identityId} and game ${req.gameId}` },
)
}

const isExpired = !!value.expires && new Date(value.expires).getTime() <= Date.now();
if (!req.reviveIfExpired && isExpired) {
throw new RuntimeError(
"presence_expired",
{ cause: `Presence expired for identity ${req.identityId} and game ${req.gameId}` },
)
}

const presence = await db.presence.update({
where: {
identityId_gameId: {
identityId: req.identityId,
gameId: req.gameId,
},
},
data: {
expires: req.expiresInMs ? new Date(Date.now() + req.expiresInMs).toISOString() : null,
},
});

return { presence };
});

return {
presence: prismaToOutput(presence),
};
}
39 changes: 39 additions & 0 deletions modules/presence/scripts/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { RuntimeError } from "../../auth/_gen/mod.ts";
import { ScriptContext } from "../_gen/scripts/get_by_game.ts";
import { prismaToOutput } from "../utils/types.ts";
import { Presence } from "../utils/types.ts";

export interface Request {
gameId: string;
identityId: string;
}

export interface Response {
presence: Presence;
}

export async function run(
ctx: ScriptContext,
req: Request,
): Promise<Response> {
const presence = await ctx.db.presence.findFirst({
where: {
gameId: req.gameId,
identityId: req.identityId,
removedAt: null,
OR: [
{ expires: { gt: new Date().toISOString() } },
{ expires: null },
],
},
});

if (!presence) {
throw new RuntimeError(
"presence_not_found",
{ cause: `Presence not found for identity ${req.identityId} and game ${req.gameId}` },
);
}

return { presence: prismaToOutput(presence) };
}
31 changes: 31 additions & 0 deletions modules/presence/scripts/get_by_game.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ScriptContext } from "../_gen/scripts/get_by_game.ts";
import { prismaToOutput } from "../utils/types.ts";
import { Presence } from "../utils/types.ts";

export interface Request {
gameId: string;
}

export interface Response {
presences: Presence[]
}

export async function run(
ctx: ScriptContext,
req: Request,
): Promise<Response> {
const matchingIdentities = await ctx.db.presence.findMany({
where: {
gameId: req.gameId,
removedAt: null,
OR: [
{ expires: { gt: new Date().toISOString() } },
{ expires: null },
],
},
});

return {
presences: matchingIdentities.map(prismaToOutput),
};
}
31 changes: 31 additions & 0 deletions modules/presence/scripts/get_by_identity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ScriptContext } from "../_gen/scripts/get_by_identity.ts";
import { prismaToOutput } from "../utils/types.ts";
import { Presence } from "../utils/types.ts";

export interface Request {
identityId: string;
}

export interface Response {
presences: Presence[]
}

export async function run(
ctx: ScriptContext,
req: Request,
): Promise<Response> {
const matchingIdentities = await ctx.db.presence.findMany({
where: {
identityId: req.identityId,
removedAt: null,
OR: [
{ expires: { gt: new Date().toISOString() } },
{ expires: null },
],
},
});

return {
presences: matchingIdentities.map(prismaToOutput),
};
}
48 changes: 48 additions & 0 deletions modules/presence/scripts/set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ScriptContext } from "../_gen/scripts/set.ts";
import { inputToPrisma, prismaToOutput } from "../utils/types.ts";
import { Presence } from "../utils/types.ts";

export type Request = Omit<Presence, "createdAt" | "updatedAt">;

export interface Response {
presence: Presence
}

export async function run(
ctx: ScriptContext,
req: Request,
): Promise<Response> {
const { presence } = await ctx.db.$transaction(async (db) => {
const resetCreatedAt = await db.presence.count({
where: {
identityId: req.identityId,
gameId: req.gameId,
OR: [
{ removedAt: { not: null } },
{ expires: { lte: new Date().toISOString() } },
],
},
});

const presence = await db.presence.upsert({
where: {
identityId_gameId: {
identityId: req.identityId,
gameId: req.gameId,
},
},
update: {
...inputToPrisma(req),
createdAt: resetCreatedAt ? new Date().toISOString() : undefined,
removedAt: null,
},
create: inputToPrisma(req),
});

return { presence };
});

return {
presence: prismaToOutput(presence),
};
}
Loading

0 comments on commit 3ce63a1

Please sign in to comment.