mirror of https://github.com/buster-so/buster.git
finalize pagination endpoint
This commit is contained in:
parent
b637bf356a
commit
d9ed8b1423
|
@ -12,14 +12,12 @@ const app = new Hono().get(
|
|||
zValidator('query', GetUserToOrganizationRequestSchema),
|
||||
async (c) => {
|
||||
const { id: userId } = c.get('busterUser');
|
||||
const { page, page_size, filters } = c.req.valid('query');
|
||||
console.log(page_size, c.req.query());
|
||||
const options = c.req.valid('query');
|
||||
|
||||
try {
|
||||
const result: GetUserToOrganizationResponse = await getUserToOrganization({
|
||||
userId,
|
||||
page,
|
||||
page_size,
|
||||
filters,
|
||||
...options,
|
||||
});
|
||||
|
||||
return c.json(result);
|
||||
|
|
|
@ -1,262 +0,0 @@
|
|||
# Pagination Utilities
|
||||
|
||||
This directory contains reusable pagination utilities for Drizzle ORM queries.
|
||||
|
||||
## Overview
|
||||
|
||||
The pagination utilities provide a consistent way to add offset-based pagination to your database queries. They handle:
|
||||
|
||||
- Page calculation and offset computation
|
||||
- Total count queries
|
||||
- Consistent response format
|
||||
- Type safety
|
||||
|
||||
## Recommended Usage
|
||||
|
||||
### Primary Method: `withPaginationMeta` (Handles Count Automatically)
|
||||
|
||||
This is the **recommended approach** for most use cases as it handles the count query for you:
|
||||
|
||||
```typescript
|
||||
import { eq, desc } from 'drizzle-orm';
|
||||
import { db } from '@/database';
|
||||
import { users } from '@/schema';
|
||||
import { withPaginationMeta } from '@/queries/shared-types';
|
||||
|
||||
const result = await withPaginationMeta({
|
||||
query: db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.active, true))
|
||||
.$dynamic(),
|
||||
orderBy: desc(users.createdAt),
|
||||
page: 2,
|
||||
page_size: 10,
|
||||
countFrom: users,
|
||||
countWhere: eq(users.active, true), // Optional: same conditions as main query
|
||||
});
|
||||
|
||||
// Result shape:
|
||||
// {
|
||||
// data: User[],
|
||||
// pagination: {
|
||||
// page: number,
|
||||
// page_size: number,
|
||||
// total: number, // Automatically calculated
|
||||
// total_pages: number // Automatically calculated
|
||||
// }
|
||||
// }
|
||||
```
|
||||
|
||||
### Simple Pagination: `withPagination` (No Count Query)
|
||||
|
||||
Use this when you only need paginated results without metadata:
|
||||
|
||||
```typescript
|
||||
import { db } from '@/database';
|
||||
import { users } from '@/schema';
|
||||
import { withPagination } from '@/queries/shared-types';
|
||||
|
||||
// Your base query needs to use .$dynamic()
|
||||
const query = db.select().from(users).$dynamic();
|
||||
|
||||
// Apply pagination
|
||||
const paginatedQuery = withPagination(query, users.createdAt, 2, 10);
|
||||
const results = await paginatedQuery;
|
||||
// Note: This only returns the data, no pagination metadata
|
||||
```
|
||||
|
||||
### Special Cases: `createPaginatedResponse` (Manual Count)
|
||||
|
||||
Only use this when you:
|
||||
- Already have the total count from another source
|
||||
- Need to transform data after querying
|
||||
- Are working with non-database data
|
||||
|
||||
```typescript
|
||||
import { createPaginatedResponse } from '@/queries/shared-types';
|
||||
|
||||
// Example: When you already have count from elsewhere
|
||||
const transformedData = existingData.map(item => ({
|
||||
...item,
|
||||
displayName: item.name || 'Unnamed'
|
||||
}));
|
||||
|
||||
const response = createPaginatedResponse({
|
||||
data: transformedData,
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
total: existingTotalCount, // You must provide this
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Types
|
||||
|
||||
#### `PaginationInput`
|
||||
```typescript
|
||||
interface PaginationInput {
|
||||
page?: number; // Current page (default: 1)
|
||||
page_size?: number; // Items per page (default: 250, max: 1000)
|
||||
}
|
||||
```
|
||||
|
||||
#### `PaginationMetadata`
|
||||
```typescript
|
||||
interface PaginationMetadata {
|
||||
page: number; // Current page
|
||||
page_size: number; // Items per page
|
||||
total: number; // Total number of items
|
||||
total_pages: number; // Total number of pages
|
||||
}
|
||||
```
|
||||
|
||||
#### `PaginatedResponse<T>`
|
||||
```typescript
|
||||
interface PaginatedResponse<T> {
|
||||
data: T[];
|
||||
pagination: PaginationMetadata;
|
||||
}
|
||||
```
|
||||
|
||||
### Functions
|
||||
|
||||
#### `withPaginationMeta(options)` ⭐ Recommended
|
||||
Executes a paginated query and automatically handles the count query.
|
||||
|
||||
#### `withPagination(query, orderBy, page?, pageSize?)`
|
||||
Adds pagination to a dynamic query without executing it or counting.
|
||||
|
||||
#### `createPaginatedResponse(options)`
|
||||
Creates a paginated response when you already have the total count.
|
||||
|
||||
#### `createPaginationMetadata(options)`
|
||||
Creates just the pagination metadata from count and page info.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use `withPaginationMeta` by default**: It handles counting automatically
|
||||
2. **Always use ordering**: Pagination without ordering can lead to inconsistent results
|
||||
3. **Match WHERE conditions**: When using `countWhere`, ensure it matches your query's WHERE clause
|
||||
4. **Consider performance**: For very large tables, consider cursor-based pagination
|
||||
5. **Set reasonable limits**: Default page size is 250, max is 1000 to prevent performance issues
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Using the Composable Approach with JOINs (Recommended)
|
||||
|
||||
The `buildPaginationQueries` helper ensures your count query has the same structure as your data query:
|
||||
|
||||
```typescript
|
||||
import { and, eq, isNull, asc } from 'drizzle-orm';
|
||||
import {
|
||||
buildPaginationQueries,
|
||||
withPaginationMeta
|
||||
} from '@/queries/shared-types';
|
||||
|
||||
// Define your WHERE condition once
|
||||
const whereCondition = and(
|
||||
eq(usersToOrganizations.organizationId, orgId),
|
||||
isNull(usersToOrganizations.deletedAt)
|
||||
);
|
||||
|
||||
// Build matching queries with the same structure
|
||||
const { dataQuery, buildCountQuery } = buildPaginationQueries({
|
||||
select: {
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
email: users.email,
|
||||
role: usersToOrganizations.role,
|
||||
status: usersToOrganizations.status,
|
||||
},
|
||||
from: users,
|
||||
joins: [
|
||||
{
|
||||
type: 'inner',
|
||||
table: usersToOrganizations,
|
||||
on: eq(users.id, usersToOrganizations.userId),
|
||||
}
|
||||
],
|
||||
where: whereCondition,
|
||||
});
|
||||
|
||||
// Use withPaginationMeta to execute both queries
|
||||
const result = await withPaginationMeta({
|
||||
query: dataQuery,
|
||||
buildCountQuery,
|
||||
orderBy: asc(users.name),
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
});
|
||||
```
|
||||
|
||||
### API Endpoint with Automatic Count
|
||||
|
||||
```typescript
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const page_size = parseInt(searchParams.get('per_page') || '25');
|
||||
|
||||
// This handles everything for you
|
||||
const result = await withPaginationMeta({
|
||||
query: db.select().from(users).$dynamic(),
|
||||
orderBy: users.createdAt,
|
||||
page,
|
||||
page_size,
|
||||
countFrom: users,
|
||||
});
|
||||
|
||||
return Response.json(result);
|
||||
}
|
||||
```
|
||||
|
||||
### With Filters
|
||||
|
||||
```typescript
|
||||
const activeUsers = await withPaginationMeta({
|
||||
query: db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(and(
|
||||
eq(users.active, true),
|
||||
like(users.email, '%@company.com')
|
||||
))
|
||||
.$dynamic(),
|
||||
orderBy: users.email,
|
||||
page,
|
||||
page_size,
|
||||
countFrom: users,
|
||||
countWhere: and( // Same conditions as query
|
||||
eq(users.active, true),
|
||||
like(users.email, '%@company.com')
|
||||
),
|
||||
});
|
||||
```
|
||||
|
||||
## Migration Guide
|
||||
|
||||
If you're updating existing code:
|
||||
|
||||
1. **Replace manual pagination logic** with `withPaginationMeta`:
|
||||
```typescript
|
||||
// Before: Manual offset/limit and count
|
||||
const offset = (page - 1) * pageSize;
|
||||
const data = await db.select().from(users).limit(pageSize).offset(offset);
|
||||
const [{ count }] = await db.select({ count: count() }).from(users);
|
||||
|
||||
// After: Automatic with withPaginationMeta
|
||||
const result = await withPaginationMeta({
|
||||
query: db.select().from(users).$dynamic(),
|
||||
orderBy: users.id,
|
||||
page,
|
||||
page_size: pageSize,
|
||||
countFrom: users,
|
||||
});
|
||||
```
|
||||
|
||||
2. **Add `.$dynamic()`** to your query builder
|
||||
3. **Update response types** to use `PaginatedResponse<T>`
|
||||
|
||||
See `pagination.example.ts` for more complete examples.
|
|
@ -15,14 +15,18 @@ import type { PaginatedResponse, PaginationInput, PaginationMetadata } from './p
|
|||
*/
|
||||
export function withPagination<T extends PgSelect>(
|
||||
qb: T,
|
||||
orderByColumn: PgColumn | SQL | SQL.Aliased,
|
||||
orderByColumn?: PgColumn | SQL | SQL.Aliased | null,
|
||||
page = 1,
|
||||
pageSize = 250
|
||||
) {
|
||||
return qb
|
||||
.orderBy(orderByColumn)
|
||||
.limit(pageSize)
|
||||
.offset((page - 1) * pageSize);
|
||||
let query = qb;
|
||||
|
||||
// Only apply orderBy if orderByColumn is provided
|
||||
if (orderByColumn) {
|
||||
query = query.orderBy(orderByColumn);
|
||||
}
|
||||
|
||||
return query.limit(pageSize).offset((page - 1) * pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,7 +42,7 @@ export function withPagination<T extends PgSelect>(
|
|||
* });
|
||||
* ```
|
||||
*/
|
||||
export function createPaginationMetadata({
|
||||
function createPaginationMetadata({
|
||||
total,
|
||||
page,
|
||||
page_size,
|
||||
|
@ -95,184 +99,3 @@ export function createPaginatedResponse<T>({
|
|||
pagination: createPaginationMetadata({ total, page, page_size }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a paginated query and returns results with pagination metadata.
|
||||
* This version properly handles queries with JOINs by requiring a separate count query builder.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Simple query
|
||||
* const result = await withPaginationMeta({
|
||||
* query: db.select().from(users).where(eq(users.active, true)).$dynamic(),
|
||||
* buildCountQuery: () => db.select({ count: count() }).from(users).where(eq(users.active, true)),
|
||||
* orderBy: users.createdAt,
|
||||
* page: 2,
|
||||
* page_size: 10,
|
||||
* });
|
||||
*
|
||||
* // Query with JOIN
|
||||
* const whereCondition = and(
|
||||
* eq(usersToOrganizations.organizationId, orgId),
|
||||
* isNull(usersToOrganizations.deletedAt)
|
||||
* );
|
||||
*
|
||||
* const result = await withPaginationMeta({
|
||||
* query: db
|
||||
* .select()
|
||||
* .from(users)
|
||||
* .innerJoin(usersToOrganizations, eq(users.id, usersToOrganizations.userId))
|
||||
* .where(whereCondition)
|
||||
* .$dynamic(),
|
||||
* buildCountQuery: () => db
|
||||
* .select({ count: count() })
|
||||
* .from(users)
|
||||
* .innerJoin(usersToOrganizations, eq(users.id, usersToOrganizations.userId))
|
||||
* .where(whereCondition),
|
||||
* orderBy: users.name,
|
||||
* page: 1,
|
||||
* page_size: 20,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function withPaginationMeta<T extends PgSelect>({
|
||||
query,
|
||||
buildCountQuery,
|
||||
orderBy,
|
||||
page = 1,
|
||||
page_size = 250,
|
||||
}: {
|
||||
query: T;
|
||||
buildCountQuery: () => PgSelect | Promise<{ count: number }[]>;
|
||||
orderBy: PgColumn | SQL | SQL.Aliased;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}): Promise<PaginatedResponse<Awaited<ReturnType<T['execute']>>[number]>> {
|
||||
// Apply pagination to the query
|
||||
const paginatedQuery = withPagination(query, orderBy, page, page_size);
|
||||
|
||||
// Execute both queries in parallel for better performance
|
||||
const [results, countResult] = await Promise.all([
|
||||
// Execute the paginated query
|
||||
paginatedQuery,
|
||||
// Execute the count query
|
||||
buildCountQuery(),
|
||||
]);
|
||||
|
||||
const total = Number((countResult as any)[0]?.count || 0);
|
||||
const total_pages = Math.ceil(total / page_size);
|
||||
|
||||
return {
|
||||
data: results,
|
||||
pagination: {
|
||||
page,
|
||||
page_size,
|
||||
total,
|
||||
total_pages,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to build matching data and count queries with the same structure.
|
||||
* This ensures your WHERE conditions and JOINs are consistent between both queries.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Build matching queries for pagination
|
||||
* const whereCondition = and(
|
||||
* eq(usersToOrganizations.organizationId, orgId),
|
||||
* isNull(usersToOrganizations.deletedAt)
|
||||
* );
|
||||
*
|
||||
* const { dataQuery, buildCountQuery } = buildPaginationQueries({
|
||||
* select: {
|
||||
* id: users.id,
|
||||
* name: users.name,
|
||||
* email: users.email,
|
||||
* avatarUrl: users.avatarUrl,
|
||||
* role: usersToOrganizations.role,
|
||||
* status: usersToOrganizations.status,
|
||||
* },
|
||||
* from: users,
|
||||
* joins: [
|
||||
* {
|
||||
* type: 'inner',
|
||||
* table: usersToOrganizations,
|
||||
* on: eq(users.id, usersToOrganizations.userId)
|
||||
* }
|
||||
* ],
|
||||
* where: whereCondition,
|
||||
* });
|
||||
*
|
||||
* // Use with withPaginationMeta
|
||||
* const result = await withPaginationMeta({
|
||||
* query: dataQuery,
|
||||
* buildCountQuery,
|
||||
* orderBy: users.name,
|
||||
* page: 1,
|
||||
* page_size: 20,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function buildPaginationQueries<
|
||||
TSelect extends Record<string, PgColumn | SQL | SQL.Aliased>,
|
||||
>({
|
||||
select,
|
||||
from,
|
||||
joins = [],
|
||||
where,
|
||||
}: {
|
||||
select: TSelect;
|
||||
from: PgTable<TableConfig>;
|
||||
joins?: Array<{
|
||||
type: 'inner' | 'left' | 'right' | 'full';
|
||||
table: PgTable<TableConfig>;
|
||||
on: SQL;
|
||||
}>;
|
||||
where?: SQL;
|
||||
}) {
|
||||
// Function to apply joins to a query
|
||||
const applyJoins = (baseQuery: any) => {
|
||||
let query = baseQuery;
|
||||
for (const join of joins) {
|
||||
switch (join.type) {
|
||||
case 'inner':
|
||||
query = query.innerJoin(join.table, join.on);
|
||||
break;
|
||||
case 'left':
|
||||
query = query.leftJoin(join.table, join.on);
|
||||
break;
|
||||
case 'right':
|
||||
query = query.rightJoin(join.table, join.on);
|
||||
break;
|
||||
case 'full':
|
||||
query = query.fullJoin(join.table, join.on);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return query;
|
||||
};
|
||||
|
||||
// Build the data query
|
||||
let dataQuery = db.select(select).from(from);
|
||||
dataQuery = applyJoins(dataQuery);
|
||||
if (where) {
|
||||
dataQuery = dataQuery.where(where) as any;
|
||||
}
|
||||
|
||||
// Build the count query function
|
||||
const buildCountQuery = () => {
|
||||
let countQuery = db.select({ count: count() }).from(from);
|
||||
countQuery = applyJoins(countQuery);
|
||||
if (where) {
|
||||
countQuery = countQuery.where(where) as any;
|
||||
}
|
||||
return countQuery;
|
||||
};
|
||||
|
||||
return {
|
||||
dataQuery: dataQuery.$dynamic(),
|
||||
buildCountQuery,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import { type InferSelectModel, and, asc, count, eq, isNull, like } from 'drizzle-orm';
|
||||
import { type InferSelectModel, SQL, and, asc, count, eq, isNull, like } from 'drizzle-orm';
|
||||
import { PgColumn } from 'drizzle-orm/pg-core';
|
||||
import { z } from 'zod';
|
||||
import { db } from '../../connection';
|
||||
import { users, usersToOrganizations } from '../../schema';
|
||||
import { getUserOrganizationId } from '../organizations/organizations';
|
||||
import {
|
||||
type PaginatedResponse,
|
||||
buildPaginationQueries,
|
||||
withPaginationMeta,
|
||||
} from '../shared-types';
|
||||
import { type PaginatedResponse, createPaginatedResponse } from '../shared-types';
|
||||
import { withPagination } from '../shared-types/with-pagination';
|
||||
|
||||
type RawOrganizationUser = InferSelectModel<typeof usersToOrganizations>;
|
||||
type RawUser = InferSelectModel<typeof users>;
|
||||
|
@ -17,78 +15,107 @@ const GetUserToOrganizationInputSchema = z.object({
|
|||
userId: z.string().uuid('User ID must be a valid UUID'),
|
||||
page: z.number().optional().default(1),
|
||||
page_size: z.number().optional().default(250),
|
||||
filters: z
|
||||
.object({
|
||||
userName: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
user_name: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
});
|
||||
|
||||
export type GetUserToOrganizationInput = z.infer<typeof GetUserToOrganizationInputSchema>;
|
||||
type GetUserToOrganizationInput = z.infer<typeof GetUserToOrganizationInputSchema>;
|
||||
|
||||
export type OrganizationUser = Pick<RawUser, 'id' | 'name' | 'email' | 'avatarUrl'> &
|
||||
type OrganizationUser = Pick<RawUser, 'id' | 'name' | 'email' | 'avatarUrl'> &
|
||||
Pick<RawOrganizationUser, 'role' | 'status'>;
|
||||
|
||||
export type GetUserToOrganizationResult = PaginatedResponse<OrganizationUser>;
|
||||
|
||||
export const getUserToOrganization = async ({
|
||||
userId,
|
||||
page = 1,
|
||||
page_size = 250,
|
||||
filters,
|
||||
}: GetUserToOrganizationInput): Promise<GetUserToOrganizationResult> => {
|
||||
// Validate input
|
||||
const validated = GetUserToOrganizationInputSchema.parse({ userId, page, page_size, filters });
|
||||
|
||||
// Get the user's organization ID
|
||||
const userOrg = await getUserOrganizationId(validated.userId);
|
||||
|
||||
if (!userOrg) {
|
||||
throw new Error('User not found in any organization');
|
||||
}
|
||||
|
||||
// Build the complete where condition
|
||||
const whereCondition = and(
|
||||
eq(usersToOrganizations.organizationId, userOrg.organizationId),
|
||||
// Helper function to build the WHERE condition for user organization queries
|
||||
function buildUserOrgWhereCondition(
|
||||
organizationId: string,
|
||||
filters?: Pick<GetUserToOrganizationInput, 'user_name' | 'email'>
|
||||
) {
|
||||
return and(
|
||||
eq(usersToOrganizations.organizationId, organizationId),
|
||||
isNull(usersToOrganizations.deletedAt),
|
||||
validated.filters?.userName ? like(users.name, `%${validated.filters.userName}%`) : undefined,
|
||||
validated.filters?.email ? like(users.email, `%${validated.filters.email}%`) : undefined
|
||||
filters?.user_name ? like(users.name, `%${filters.user_name}%`) : undefined,
|
||||
filters?.email ? like(users.email, `%${filters.email}%`) : undefined
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function to build the base query with joins
|
||||
function buildUserOrgBaseQuery<T extends Record<string, PgColumn | SQL>>(selectColumns: T) {
|
||||
return db
|
||||
.select(selectColumns)
|
||||
.from(users)
|
||||
.innerJoin(usersToOrganizations, eq(users.id, usersToOrganizations.userId));
|
||||
}
|
||||
|
||||
// Helper function to get the total count of users in an organization
|
||||
async function getUserToOrganizationTotal(
|
||||
organizationId: string,
|
||||
filters?: Pick<GetUserToOrganizationInput, 'user_name' | 'email'>
|
||||
): Promise<number> {
|
||||
try {
|
||||
const query = buildUserOrgBaseQuery({ count: count() }).where(
|
||||
buildUserOrgWhereCondition(organizationId, filters)
|
||||
);
|
||||
const result = await query;
|
||||
return result[0]?.count ?? 0;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export const getUserToOrganization = async (
|
||||
params: GetUserToOrganizationInput
|
||||
): Promise<PaginatedResponse<OrganizationUser>> => {
|
||||
// Validate input
|
||||
const { user_name, email, page, page_size, userId } =
|
||||
GetUserToOrganizationInputSchema.parse(params);
|
||||
const filters = {
|
||||
user_name,
|
||||
email,
|
||||
};
|
||||
// Get the user's organization ID
|
||||
const { organizationId } = await getUserOrganizationId(userId)
|
||||
.then((userOrg) => {
|
||||
if (!userOrg || !userOrg.organizationId) {
|
||||
throw new Error('User not found in any organization');
|
||||
}
|
||||
return { organizationId: userOrg.organizationId };
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
throw new Error('Error fetching user organization');
|
||||
});
|
||||
|
||||
try {
|
||||
// Use the new composable approach to build matching queries
|
||||
const { dataQuery, buildCountQuery } = buildPaginationQueries({
|
||||
select: {
|
||||
// Build and execute the data query using shared helpers
|
||||
const dataQuery = withPagination(
|
||||
buildUserOrgBaseQuery({
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
email: users.email,
|
||||
avatarUrl: users.avatarUrl,
|
||||
role: usersToOrganizations.role,
|
||||
status: usersToOrganizations.status,
|
||||
},
|
||||
from: users,
|
||||
joins: [
|
||||
{
|
||||
type: 'inner',
|
||||
table: usersToOrganizations,
|
||||
on: eq(users.id, usersToOrganizations.userId),
|
||||
},
|
||||
],
|
||||
...(whereCondition && { where: whereCondition }),
|
||||
});
|
||||
})
|
||||
.where(buildUserOrgWhereCondition(organizationId, filters))
|
||||
.$dynamic(),
|
||||
asc(users.name),
|
||||
page,
|
||||
page_size
|
||||
);
|
||||
|
||||
// Use withPaginationMeta to handle pagination and counting
|
||||
const result = await withPaginationMeta({
|
||||
query: dataQuery,
|
||||
buildCountQuery,
|
||||
orderBy: asc(users.name),
|
||||
page: validated.page,
|
||||
page_size: validated.page_size,
|
||||
});
|
||||
// Execute queries in parallel for better performance
|
||||
const [data, total] = await Promise.all([
|
||||
dataQuery,
|
||||
getUserToOrganizationTotal(organizationId, filters),
|
||||
]);
|
||||
|
||||
// Return the result directly - it already has the correct format
|
||||
return result;
|
||||
// Use the simple createPaginatedResponse helper
|
||||
return createPaginatedResponse({
|
||||
data,
|
||||
page,
|
||||
page_size,
|
||||
total,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error('Error fetching users');
|
||||
|
|
|
@ -51,13 +51,8 @@ export type GetUserListRequest = z.infer<typeof GetUserListRequestSchema>;
|
|||
export const GetUserToOrganizationRequestSchema = z.object({
|
||||
page: z.coerce.number().min(1).optional().default(1),
|
||||
page_size: z.coerce.number().min(1).max(5000).optional().default(250),
|
||||
filters: z
|
||||
.object({
|
||||
user_name: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
})
|
||||
.default({})
|
||||
.optional(),
|
||||
user_name: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
});
|
||||
|
||||
export type GetUserToOrganizationRequest = z.infer<typeof GetUserToOrganizationRequestSchema>;
|
||||
|
|
Loading…
Reference in New Issue