Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions apps/dashboard/app/api/activity/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,28 @@ export async function GET(request: Request): Promise<NextResponse> {
const guildId = getActiveGuildId(request);

try {
// Repository-sourced events are already guild-scoped at the choke point
// (IActivityRepository.query requires guildId). Storage-sourced (webhook)
// and seed events aren't tagged at write time yet, so they still rely on
// the best-effort metadata.guildId filter — see docs/multi-tenancy.md.
const repositoryEvents = await getActivityRepository()
.query({})
.query(guildId, {})
.catch((error) => {
console.error("Error fetching repository activity:", error);
return [];
});
const storageEvents = await activityStorage.getEvents();
const seedEvents = mockActivity.map(mockActivityToEvent);
const scopedStorageAndSeed = filterActivityEventsByGuild([...storageEvents, ...seedEvents], guildId);

const seen = new Set<string>();
const merged = [...repositoryEvents, ...storageEvents, ...seedEvents].filter((event) => {
const merged = [...repositoryEvents, ...scopedStorageAndSeed].filter((event) => {
if (seen.has(event.id)) return false;
seen.add(event.id);
return true;
});

// Tenant scope first, then apply list filters / pagination.
const scoped = filterActivityEventsByGuild(merged, guildId);
const result = filterActivityEvents(scoped, parsed.value);
const result = filterActivityEvents(merged, parsed.value);
return apiResponse(result);
} catch (error) {
console.error("Error fetching activity:", error);
Expand Down
6 changes: 3 additions & 3 deletions apps/dashboard/app/api/guilds/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export async function POST(request: Request): Promise<NextResponse> {
passCount: validData.passCount,
});

await recordDashboardActivity({
await recordDashboardActivity(created.id, {
type: "guild.created",
entity: { type: "guild", id: created.id, name: created.name },
actor: { id: session.userId, name: session.name },
Expand Down Expand Up @@ -100,7 +100,7 @@ export async function PATCH(request: Request): Promise<NextResponse> {
const guildRepository = getGuildRepository();
const updated = await guildRepository.update(id, body);
if (!updated) throw new Error("Guild not found or update failed");
await recordDashboardActivity({
await recordDashboardActivity(id, {
type: "guild.updated",
entity: { type: "guild", id: updated.id, name: updated.name },
actor: { id: session.userId, name: session.name },
Expand Down Expand Up @@ -128,7 +128,7 @@ export async function DELETE(request: Request): Promise<NextResponse> {
if (!guild) throw new Error("Guild not found or deletion failed");
const success = await guildRepository.delete(id);
if (!success) throw new Error("Guild not found or deletion failed");
await recordDashboardActivity({
await recordDashboardActivity(id, {
type: "guild.deleted",
entity: { type: "guild", id: guild.id, name: guild.name },
actor: { id: session.userId, name: session.name },
Expand Down
9 changes: 5 additions & 4 deletions apps/dashboard/app/api/members/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,9 @@ export async function POST(request: Request): Promise<NextResponse> {
}

const memberRepository = getMemberRepository();
const created = await memberRepository.create(getActiveGuildId(request), validation.data);
await recordDashboardActivity({
const guildId = getActiveGuildId(request);
const created = await memberRepository.create(guildId, validation.data);
await recordDashboardActivity(guildId, {
type: "member.joined",
entity: { type: "member", id: created.id, name: created.name },
actor: { id: session.userId, name: session.name },
Expand Down Expand Up @@ -207,7 +208,7 @@ export async function PATCH(request: Request): Promise<NextResponse> {
if (!updated) throw new NotFoundError("Member not found.");
const rolesChanged = existing && validation.data.roles && JSON.stringify(existing.roles) !== JSON.stringify(validation.data.roles);
if (rolesChanged) {
await recordDashboardActivity({
await recordDashboardActivity(guildId, {
type: "member.roles_changed",
entity: { type: "member", id: updated.id, name: updated.name },
actor: { id: session.userId, name: session.name },
Expand Down Expand Up @@ -238,7 +239,7 @@ export async function DELETE(request: Request): Promise<NextResponse> {
if (!member) throw new NotFoundError("Member not found.");
const success = await memberRepository.delete(guildId, id);
if (!success) throw new NotFoundError("Member not found.");
await recordDashboardActivity({
await recordDashboardActivity(guildId, {
type: "member.left",
entity: { type: "member", id: member.id, name: member.name },
actor: { id: session.userId, name: session.name },
Expand Down
12 changes: 7 additions & 5 deletions apps/dashboard/app/api/passes/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ export async function POST(request: Request): Promise<NextResponse> {
}

const passRepository = getPassRepository();
const created = await passRepository.create(getActiveGuildId(request), validation.data);
await recordDashboardActivity({
const guildId = getActiveGuildId(request);
const created = await passRepository.create(guildId, validation.data);
await recordDashboardActivity(guildId, {
type: "pass.created",
entity: { type: "pass", id: created.id, name: created.name },
actor: { id: session.userId, name: session.name },
Expand Down Expand Up @@ -127,9 +128,10 @@ export async function PATCH(request: Request): Promise<NextResponse> {
}

const passRepository = getPassRepository();
const updated = await passRepository.update(getActiveGuildId(request), id, validation.data);
const guildId = getActiveGuildId(request);
const updated = await passRepository.update(guildId, id, validation.data);
if (!updated) throw new NotFoundError("Pass not found.");
await recordDashboardActivity({
await recordDashboardActivity(guildId, {
type: "pass.updated",
entity: { type: "pass", id: updated.id, name: updated.name },
actor: { id: session.userId, name: session.name },
Expand Down Expand Up @@ -159,7 +161,7 @@ export async function DELETE(request: Request): Promise<NextResponse> {
if (!pass) throw new NotFoundError("Pass not found.");
const success = await passRepository.delete(guildId, id);
if (!success) throw new NotFoundError("Pass not found.");
await recordDashboardActivity({
await recordDashboardActivity(guildId, {
type: "pass.deleted",
entity: { type: "pass", id: pass.id, name: pass.name },
actor: { id: session.userId, name: session.name },
Expand Down
5 changes: 3 additions & 2 deletions apps/dashboard/app/api/settings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export async function GET(request: Request): Promise<NextResponse> {
}

export async function PATCH(request: Request): Promise<NextResponse> {
const guard = await requireSessionAndPermission(request, getActiveGuildId(request), "settings:write");
const guildId = getActiveGuildId(request);
const guard = await requireSessionAndPermission(request, guildId, "settings:write");
if (!guard.ok) return guard.response;
const { session } = guard;

Expand All @@ -36,7 +37,7 @@ export async function PATCH(request: Request): Promise<NextResponse> {

return handleApiError(async () => {
const updated = await getSettingsRepository().update(validation.value);
await recordDashboardActivity({
await recordDashboardActivity(guildId, {
type: "settings.updated",
actor: { id: session.userId, name: session.name },
description: "Dashboard settings updated",
Expand Down
3 changes: 2 additions & 1 deletion apps/dashboard/lib/activity/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ function actorFromSession(session?: Session): { id?: string; name?: string; wall
}

export async function recordDashboardActivity(
guildId: string,
input: DashboardActivityInput
): Promise<ActivityEvent> {
const event = await getActivityRepository().append({
const event = await getActivityRepository().append(guildId, {
type: input.type,
source: "dashboard",
severity: input.severity ?? "info",
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/lib/auth/require-permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export async function requireSessionAndPermission(
}

function recordPermissionDenied(session: Session, guildId: string, permission: Permission): void {
void recordDashboardActivity({
void recordDashboardActivity(guildId, {
type: "activity.permission_denied",
severity: "warning",
actor: { id: session.userId, name: session.name },
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/lib/reconciliation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ async function recordReconciliationAudit(
.map((d) => `${d.field}: ${d.storedValue} → ${d.actualValue}`)
.join("; ");

await activityRepo.append({
await activityRepo.append(guildId, {
type: "guild.updated",
source: "dashboard",
severity: "warning",
Expand Down
Loading