Swadhin Biswas Feature/ai code reviewer (#73) · 8/2/2026
105a287
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/**
* SCIM 2.0 Groups API Endpoint
* GET /api/scim/v2/Groups - Search/List teams/groups
* POST /api/scim/v2/Groups - Create group/team
*/
import type { APIRoute } from "astro";
import { getDatabase, schema } from "@/db";
import { eq } from "drizzle-orm";
import {
formatSCIMGroup,
createSCIMError,
SCIM_SCHEMAS,
type SCIMListResponse,
} from "@/lib/scim";
import { logger } from "@/lib/logger";
import { generateId } from "@/lib/utils";
export const GET: APIRoute = async ({ request, url }) => {
try {
const baseUrl = new URL(url).origin;
const countParam = parseInt(url.searchParams.get("count") || "100", 10);
const startIndex = parseInt(url.searchParams.get("startIndex") || "1", 10);
const db = getDatabase();
const allTeams = await db.query.teams.findMany({
limit: countParam,
with: {
members: true,
},
});
const resources = allTeams.map((team) =>
formatSCIMGroup(team, team.members || [], baseUrl)
);
const response: SCIMListResponse<any> = {
schemas: [SCIM_SCHEMAS.LIST_RESPONSE],
totalResults: resources.length,
startIndex,
itemsPerPage: countParam,
Resources: resources,
};
return new Response(JSON.stringify(response), {
status: 200,
headers: {
"Content-Type": "application/scim+json",
},
});
} catch (err: any) {
logger.error({ err: err.message }, "SCIM GET Groups error");
return new Response(JSON.stringify(createSCIMError(500, err.message)), {
status: 500,
headers: { "Content-Type": "application/scim+json" },
});
}
};
export const POST: APIRoute = async ({ request, url }) => {
try {
const baseUrl = new URL(url).origin;
const body = await request.json();
if (!body.displayName) {
return new Response(JSON.stringify(createSCIMError(400, "displayName is required")), {
status: 400,
headers: { "Content-Type": "application/scim+json" },
});
}
const db = getDatabase();
// Get default org or create ID
const org = await db.query.organizations.findFirst();
if (!org) {
return new Response(JSON.stringify(createSCIMError(400, "No organization exists for group creation")), {
status: 400,
headers: { "Content-Type": "application/scim+json" },
});
}
const teamId = generateId();
const slug = body.displayName.toLowerCase().replace(/[^a-z0-9_-]/g, "-");
// @ts-expect-error - Drizzle multi-db union type issue
await db.insert(schema.teams).values({
id: teamId,
organizationId: org.id,
name: body.displayName,
slug,
description: "Provisioned via SCIM 2.0",
createdAt: new Date(),
updatedAt: new Date(),
});
// Add members if provided
if (body.members && Array.isArray(body.members)) {
for (const member of body.members) {
if (member.value) {
try {
// @ts-expect-error - Drizzle multi-db union type issue
await db.insert(schema.teamMembers).values({
teamId,
userId: member.value,
role: "member",
createdAt: new Date(),
});
} catch (e) {
// Ignore duplicate member insertions
}
}
}
}
const newTeam = await db.query.teams.findFirst({
where: eq(schema.teams.id, teamId),
with: { members: true },
});
return new Response(JSON.stringify(formatSCIMGroup(newTeam, newTeam?.members || [], baseUrl)), {
status: 201,
headers: {
"Content-Type": "application/scim+json",
},
});
} catch (err: any) {
logger.error({ err: err.message }, "SCIM POST Groups error");
return new Response(JSON.stringify(createSCIMError(400, err.message)), {
status: 400,
headers: { "Content-Type": "application/scim+json" },
});
}
};