use niceId for client routes

This commit is contained in:
miloschwartz
2025-12-06 20:31:09 -05:00
parent 8a8c0edad3
commit d7e06161a8
22 changed files with 375 additions and 222 deletions

View File

@@ -25,6 +25,7 @@ import { listExitNodes } from "#dynamic/lib/exitNodes";
import { generateId } from "@server/auth/sessions/app";
import { OpenAPITags, registry } from "@server/openApi";
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
import { getUniqueClientName } from "@server/db/names";
const createClientParamsSchema = z.strictObject({
orgId: z.string()
@@ -206,9 +207,12 @@ export async function createClient(
);
}
const niceId = await getUniqueClientName(orgId);
[newClient] = await trx
.insert(clients)
.values({
niceId,
exitNodeId: randomExitNode.exitNodeId,
orgId,
name,

View File

@@ -12,22 +12,34 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const getClientSchema = z.strictObject({
clientId: z.string().transform(stoi).pipe(z.int().positive())
clientId: z
.string()
.optional()
.transform(stoi)
.pipe(z.int().positive().optional())
.optional(),
niceId: z.string().optional(),
orgId: z.string().optional()
});
async function query(clientId: number) {
// Get the client
const [client] = await db
.select()
.from(clients)
.where(and(eq(clients.clientId, clientId)))
.leftJoin(olms, eq(clients.olmId, olms.olmId))
.limit(1);
if (!client) {
return null;
async function query(clientId?: number, niceId?: string, orgId?: string) {
if (clientId) {
const [res] = await db
.select()
.from(clients)
.where(eq(clients.clientId, clientId))
.leftJoin(olms, eq(clients.clientId, olms.clientId))
.limit(1);
return res;
} else if (niceId && orgId) {
const [res] = await db
.select()
.from(clients)
.where(and(eq(clients.niceId, niceId), eq(clients.orgId, orgId)))
.leftJoin(olms, eq(olms.clientId, olms.clientId))
.limit(1);
return res;
}
return client;
}
export type GetClientResponse = NonNullable<
@@ -36,13 +48,30 @@ export type GetClientResponse = NonNullable<
olmId: string | null;
};
registry.registerPath({
method: "get",
path: "/org/{orgId}/client/{niceId}",
description:
"Get a client by orgId and niceId. NiceId is a readable ID for the site and unique on a per org basis.",
tags: [OpenAPITags.Org, OpenAPITags.Site],
request: {
params: z.object({
orgId: z.string(),
niceId: z.string()
})
},
responses: {}
});
registry.registerPath({
method: "get",
path: "/client/{clientId}",
description: "Get a client by its client ID.",
tags: [OpenAPITags.Client],
request: {
params: getClientSchema
params: z.object({
clientId: z.number()
})
},
responses: {}
});
@@ -66,9 +95,9 @@ export async function getClient(
);
}
const { clientId } = parsedParams.data;
const { clientId, niceId, orgId } = parsedParams.data;
const client = await query(clientId);
const client = await query(clientId, niceId, orgId);
if (!client) {
return next(

View File

@@ -128,7 +128,8 @@ function queryClients(orgId: string, accessibleClientIds: number[], filter?: "us
olmVersion: olms.version,
userId: clients.userId,
username: users.username,
userEmail: users.email
userEmail: users.email,
niceId: clients.niceId
})
.from(clients)
.leftJoin(orgs, eq(clients.orgId, orgs.orgId))

View File

@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { Client, db, exitNodes, olms, sites } from "@server/db";
import { clients, clientSitesAssociationsCache } from "@server/db";
import { db } from "@server/db";
import { clients } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
@@ -15,7 +15,8 @@ const updateClientParamsSchema = z.strictObject({
});
const updateClientSchema = z.strictObject({
name: z.string().min(1).max(255).optional()
name: z.string().min(1).max(255).optional(),
niceId: z.string().min(1).max(255).optional()
});
export type UpdateClientBody = z.infer<typeof updateClientSchema>;
@@ -54,7 +55,7 @@ export async function updateClient(
);
}
const { name } = parsedBody.data;
const { name, niceId } = parsedBody.data;
const parsedParams = updateClientParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
@@ -84,9 +85,32 @@ export async function updateClient(
);
}
// if niceId is provided, check if it's already in use by another client
if (niceId) {
const [existingClient] = await db
.select()
.from(clients)
.where(
and(
eq(clients.niceId, niceId),
eq(clients.orgId, clients.orgId)
)
)
.limit(1);
if (existingClient) {
return next(
createHttpError(
HttpCode.CONFLICT,
`A client with niceId "${niceId}" already exists`
)
);
}
}
const updatedClient = await db
.update(clients)
.set({ name })
.set({ name, niceId })
.where(eq(clients.clientId, clientId))
.returning();