Add search / filter box (#2170)

* feat: Add search client based on #1978

* moved the filtering to the DB level using zod and tidied up some imports.

* minor fix

* minor fix

* fix typo

---------

Co-authored-by: Bernd Storath <999999bst@gmail.com>
This commit is contained in:
YuWorm
2025-10-20 14:04:21 +08:00
committed by GitHub
parent 76d5944726
commit 2b42b639ea
14 changed files with 225 additions and 15 deletions
@@ -1,4 +1,4 @@
import { eq, sql } from 'drizzle-orm';
import { eq, sql, or, like, and } from 'drizzle-orm';
import { containsCidr, parseCidr } from 'cidr-tools';
import { client } from './schema';
import type {
@@ -42,6 +42,39 @@ function createPreparedStatement(db: DBType) {
},
})
.prepare(),
findAllPublicFiltered: db.query.client
.findMany({
where: or(
like(client.name, sql.placeholder('filter')),
like(client.ipv4Address, sql.placeholder('filter')),
like(client.ipv6Address, sql.placeholder('filter'))
),
with: {
oneTimeLink: true,
},
columns: {
privateKey: false,
preSharedKey: false,
},
})
.prepare(),
findByUserIdFiltered: db.query.client
.findMany({
where: and(
eq(client.userId, sql.placeholder('userId')),
or(
like(client.name, sql.placeholder('filter')),
like(client.ipv4Address, sql.placeholder('filter')),
like(client.ipv6Address, sql.placeholder('filter'))
)
),
with: { oneTimeLink: true },
columns: {
privateKey: false,
preSharedKey: false,
},
})
.prepare(),
toggle: db
.update(client)
.set({ enabled: sql.placeholder('enabled') as never as boolean })
@@ -96,6 +129,41 @@ export class ClientService {
}));
}
/**
* Get clients based on user ID and filter conditions
*/
async getForUserFiltered(userId: ID, filter: string) {
const filterPattern = `%${filter.toLowerCase()}%`;
const result = await this.#statements.findByUserIdFiltered.execute({
userId,
filter: filterPattern,
});
return result.map((row) => ({
...row,
createdAt: new Date(row.createdAt),
updatedAt: new Date(row.updatedAt),
}));
}
/**
* Get all clients based on filter conditions without sensitive data
*/
async getAllPublicFiltered(filter: string) {
const filterPattern = `%${filter.toLowerCase()}%`;
const result = await this.#statements.findAllPublicFiltered.execute({
filter: filterPattern,
});
return result.map((row) => ({
...row,
createdAt: new Date(row.createdAt),
updatedAt: new Date(row.updatedAt),
}));
}
get(id: ID) {
return this.#statements.findById.execute({ id });
}
@@ -39,6 +39,8 @@ const address6 = z
.min(1, { message: t('zod.client.address6') })
.pipe(safeStringRefine);
const filter = z.string().optional();
const serverAllowedIps = z.array(AddressSchema, {
message: t('zod.client.serverAllowedIps'),
});
@@ -50,6 +52,12 @@ export const ClientCreateSchema = z.object({
export type ClientCreateType = z.infer<typeof ClientCreateSchema>;
export const ClientQuerySchema = z.object({
filter: filter,
});
export type ClientQueryType = z.infer<typeof ClientQuerySchema>;
export const ClientUpdateSchema = schemaForType<UpdateClientType>()(
z.object({
name: name,