Skip to content

Commit f476f78

Browse files
committed
Update feature flags API endpoint
1 parent 2b2003b commit f476f78

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
2+
import { prisma } from "~/db.server";
3+
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
4+
import { getRunsReplicationGlobal } from "~/services/runsReplicationGlobal.server";
5+
import { runsReplicationInstance } from "~/services/runsReplicationInstance.server";
6+
import {
7+
makeSetFlags,
8+
setFlags,
9+
FeatureFlagCatalogSchema,
10+
validateAllFeatureFlags,
11+
validatePartialFeatureFlags,
12+
makeSetMultipleFlags,
13+
} from "~/v3/featureFlags.server";
14+
import { z } from "zod";
15+
16+
export async function action({ request }: ActionFunctionArgs) {
17+
// Next authenticate the request
18+
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
19+
20+
if (!authenticationResult) {
21+
return json({ error: "Invalid or Missing API key" }, { status: 401 });
22+
}
23+
24+
const user = await prisma.user.findUnique({
25+
where: {
26+
id: authenticationResult.userId,
27+
},
28+
});
29+
30+
if (!user) {
31+
return json({ error: "Invalid or Missing API key" }, { status: 401 });
32+
}
33+
34+
if (!user.admin) {
35+
return json({ error: "You must be an admin to perform this action" }, { status: 403 });
36+
}
37+
38+
try {
39+
// Parse the request body
40+
const body = await request.json();
41+
42+
// Validate the input using the partial schema
43+
const validationResult = validatePartialFeatureFlags(body as Record<string, unknown>);
44+
if (!validationResult.success) {
45+
return json(
46+
{
47+
error: "Invalid feature flags data",
48+
details: validationResult.error.issues,
49+
},
50+
{ status: 400 }
51+
);
52+
}
53+
54+
const featureFlags = validationResult.data;
55+
const setMultipleFlags = makeSetMultipleFlags(prisma);
56+
const updatedFlags = await setMultipleFlags(featureFlags);
57+
58+
return json({
59+
success: true,
60+
updatedFlags,
61+
message: `Updated ${updatedFlags.length} feature flag(s)`,
62+
});
63+
} catch (error) {
64+
return json(
65+
{
66+
error: error instanceof Error ? error.message : String(error),
67+
},
68+
{ status: 400 }
69+
);
70+
}
71+
}

apps/webapp/app/v3/featureFlags.server.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,46 @@ export function makeSetFlags(_prisma: PrismaClientOrTransaction = prisma) {
6767

6868
export const flags = makeFlags();
6969
export const setFlags = makeSetFlags();
70+
71+
// Create a Zod schema from the existing catalog
72+
export const FeatureFlagCatalogSchema = z.object(FeatureFlagCatalog);
73+
74+
// Utility function to validate a feature flag value
75+
export function validateFeatureFlagValue<T extends FeatureFlagKey>(
76+
key: T,
77+
value: unknown
78+
): z.SafeParseReturnType<unknown, z.infer<(typeof FeatureFlagCatalog)[T]>> {
79+
return FeatureFlagCatalog[key].safeParse(value);
80+
}
81+
82+
// Utility function to validate all feature flags at once
83+
export function validateAllFeatureFlags(values: Record<string, unknown>) {
84+
return FeatureFlagCatalogSchema.safeParse(values);
85+
}
86+
87+
// Utility function to validate partial feature flags (all keys optional)
88+
export function validatePartialFeatureFlags(values: Record<string, unknown>) {
89+
return FeatureFlagCatalogSchema.partial().safeParse(values);
90+
}
91+
92+
// Utility function to set multiple feature flags at once
93+
export function makeSetMultipleFlags(_prisma: PrismaClientOrTransaction = prisma) {
94+
return async function setMultipleFlags(
95+
flags: Partial<z.infer<typeof FeatureFlagCatalogSchema>>
96+
): Promise<{ key: string; value: any }[]> {
97+
const setFlag = makeSetFlags(_prisma);
98+
const updatedFlags: { key: string; value: any }[] = [];
99+
100+
for (const [key, value] of Object.entries(flags)) {
101+
if (value !== undefined) {
102+
await setFlag({
103+
key: key as any,
104+
value: value as any,
105+
});
106+
updatedFlags.push({ key, value });
107+
}
108+
}
109+
110+
return updatedFlags;
111+
};
112+
}

0 commit comments

Comments
 (0)