You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

134 lines
4.1 KiB

import { dbGlobal } from "drizzle-pkg/lib/db";
import { timelineEvents } from "drizzle-pkg/lib/schema/content";
import { users } from "drizzle-pkg/lib/schema/auth";
import { and, desc, eq } from "drizzle-orm";
import { visibilitySchema, type Visibility } from "#server/constants/visibility";
import { visibilityShareToken } from "#server/utils/share-token";
import { nextIntegerId } from "#server/utils/sqlite-id";
export async function listTimelineForUser(userId: number) {
return dbGlobal
.select()
.from(timelineEvents)
.where(eq(timelineEvents.userId, userId))
.orderBy(desc(timelineEvents.occurredOn), desc(timelineEvents.id));
}
export async function getTimelineForUser(userId: number, id: number) {
const [row] = await dbGlobal
.select()
.from(timelineEvents)
.where(and(eq(timelineEvents.id, id), eq(timelineEvents.userId, userId)))
.limit(1);
return row ?? null;
}
export async function createTimelineEvent(
userId: number,
input: {
occurredOn: Date;
title: string;
bodyMarkdown?: string | null;
linkUrl?: string | null;
visibility: Visibility;
},
) {
const vis = visibilitySchema.parse(input.visibility);
const shareToken = visibilityShareToken(vis, null);
const id = await nextIntegerId(timelineEvents, timelineEvents.id);
await dbGlobal.insert(timelineEvents).values({
id,
userId,
occurredOn: input.occurredOn,
title: input.title.trim(),
bodyMarkdown: input.bodyMarkdown?.trim() || undefined,
linkUrl: input.linkUrl?.trim() || undefined,
visibility: vis,
shareToken,
});
return getTimelineForUser(userId, id);
}
export async function updateTimelineEvent(
userId: number,
id: number,
patch: Partial<{
occurredOn: Date;
title: string;
bodyMarkdown: string | null;
linkUrl: string | null;
visibility: Visibility;
}>,
) {
const existing = await getTimelineForUser(userId, id);
if (!existing) {
return null;
}
const nextVis =
patch.visibility !== undefined
? visibilitySchema.parse(patch.visibility)
: (existing.visibility as Visibility);
const shareToken =
patch.visibility !== undefined
? visibilityShareToken(nextVis, existing.shareToken)
: existing.shareToken;
await dbGlobal
.update(timelineEvents)
.set({
...(patch.occurredOn !== undefined ? { occurredOn: patch.occurredOn } : {}),
...(patch.title !== undefined ? { title: patch.title.trim() } : {}),
...(patch.bodyMarkdown !== undefined
? { bodyMarkdown: patch.bodyMarkdown?.trim() || null }
: {}),
...(patch.linkUrl !== undefined ? { linkUrl: patch.linkUrl?.trim() || null } : {}),
...(patch.visibility !== undefined ? { visibility: nextVis, shareToken } : {}),
})
.where(and(eq(timelineEvents.id, id), eq(timelineEvents.userId, userId)));
return getTimelineForUser(userId, id);
}
export async function deleteTimelineEvent(userId: number, id: number) {
const existing = await getTimelineForUser(userId, id);
if (!existing) {
return false;
}
await dbGlobal
.delete(timelineEvents)
.where(and(eq(timelineEvents.id, id), eq(timelineEvents.userId, userId)));
return true;
}
export async function listPublicTimelineBySlug(publicSlug: string) {
const rows = await dbGlobal
.select({ ev: timelineEvents })
.from(timelineEvents)
.innerJoin(users, eq(timelineEvents.userId, users.id))
.where(
and(
eq(users.publicSlug, publicSlug),
eq(users.status, "active"),
eq(timelineEvents.visibility, "public"),
),
)
.orderBy(desc(timelineEvents.occurredOn), desc(timelineEvents.id));
return rows.map((r) => r.ev);
}
export async function getUnlistedTimeline(publicSlug: string, shareToken: string) {
const [row] = await dbGlobal
.select({ ev: timelineEvents })
.from(timelineEvents)
.innerJoin(users, eq(timelineEvents.userId, users.id))
.where(
and(
eq(users.publicSlug, publicSlug),
eq(users.status, "active"),
eq(timelineEvents.visibility, "unlisted"),
eq(timelineEvents.shareToken, shareToken),
),
)
.limit(1);
return row?.ev ?? null;
}