Pagination View fixes for DSync User/Group Lists (#2572)

* Handle pagination query params correctly and set response header for pageToken

* Support for pageToken

* Revert tokenmap change for and add comment

* Exclude `log_webhook_events` checkbox while creating

* `pageToken` handling for WebhookLogs

* `pageToken` handling in API route

* Fix unit tests

* Fix test

* Update tokenmap using effect
This commit is contained in:
Aswin V 2024-04-15 15:31:32 +05:30 committed by GitHub
parent e6ec996234
commit fde514123b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 145 additions and 75 deletions

View File

@ -47,7 +47,7 @@ const CreateDirectory = ({
excludeFields={ excludeFields={
setupLinkToken setupLinkToken
? ['name', 'tenant', 'product', 'webhook_url', 'webhook_secret', 'log_webhook_events'] ? ['name', 'tenant', 'product', 'webhook_url', 'webhook_secret', 'log_webhook_events']
: undefined : ['log_webhook_events']
} }
urls={{ urls={{
post: setupLinkToken post: setupLinkToken

View File

@ -1,13 +1,14 @@
import useSWR from 'swr'; import useSWR from 'swr';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import EyeIcon from '@heroicons/react/24/outline/EyeIcon'; import EyeIcon from '@heroicons/react/24/outline/EyeIcon';
import type { Group } from '../types'; import type { ApiSuccess, Group } from '../types';
import { fetcher, addQueryParamsToPath } from '../utils'; import { fetcher, addQueryParamsToPath } from '../utils';
import { DirectoryTab } from '../dsync'; import { DirectoryTab } from '../dsync';
import { usePaginate, useDirectory } from '../hooks'; import { usePaginate, useDirectory } from '../hooks';
import { TableBodyType } from '../shared/Table'; import { TableBodyType } from '../shared/Table';
import { Loading, Table, EmptyState, Error, Pagination, PageHeader, pageLimit } from '../shared'; import { Loading, Table, EmptyState, Error, Pagination, PageHeader, pageLimit } from '../shared';
import { useRouter } from '../hooks'; import { useRouter } from '../hooks';
import { useEffect } from 'react';
export const DirectoryGroups = ({ export const DirectoryGroups = ({
urls, urls,
@ -18,7 +19,7 @@ export const DirectoryGroups = ({
}) => { }) => {
const { router } = useRouter(); const { router } = useRouter();
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const { paginate, setPaginate, pageTokenMap } = usePaginate(router!); const { paginate, setPaginate, pageTokenMap, setPageTokenMap } = usePaginate(router!);
const params = { const params = {
pageOffset: paginate.offset, pageOffset: paginate.offset,
@ -26,6 +27,7 @@ export const DirectoryGroups = ({
}; };
// For DynamoDB // For DynamoDB
// Use the (next)pageToken mapped to the previous page offset to get the current page
if (paginate.offset > 0 && pageTokenMap[paginate.offset - pageLimit]) { if (paginate.offset > 0 && pageTokenMap[paginate.offset - pageLimit]) {
params['pageToken'] = pageTokenMap[paginate.offset - pageLimit]; params['pageToken'] = pageTokenMap[paginate.offset - pageLimit];
} }
@ -33,7 +35,15 @@ export const DirectoryGroups = ({
const getUrl = addQueryParamsToPath(urls.getGroups, params); const getUrl = addQueryParamsToPath(urls.getGroups, params);
const { directory, isLoadingDirectory, directoryError } = useDirectory(urls.getDirectory); const { directory, isLoadingDirectory, directoryError } = useDirectory(urls.getDirectory);
const { data, isLoading, error } = useSWR<{ data: Group[] }>(getUrl, fetcher); const { data, isLoading, error } = useSWR<ApiSuccess<Group[]>>(getUrl, fetcher);
const nextPageToken = data?.pageToken;
useEffect(() => {
if (nextPageToken) {
setPageTokenMap((tokenMap) => ({ ...tokenMap, [paginate.offset]: nextPageToken }));
}
}, [nextPageToken, paginate.offset]);
if (isLoading || isLoadingDirectory) { if (isLoading || isLoadingDirectory) {
return <Loading />; return <Loading />;

View File

@ -1,13 +1,14 @@
import useSWR from 'swr'; import useSWR from 'swr';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import EyeIcon from '@heroicons/react/24/outline/EyeIcon'; import EyeIcon from '@heroicons/react/24/outline/EyeIcon';
import type { User } from '../types'; import type { ApiSuccess, User } from '../types';
import { addQueryParamsToPath, fetcher } from '../utils'; import { addQueryParamsToPath, fetcher } from '../utils';
import { DirectoryTab } from '../dsync'; import { DirectoryTab } from '../dsync';
import { usePaginate, useDirectory } from '../hooks'; import { usePaginate, useDirectory } from '../hooks';
import { TableBodyType } from '../shared/Table'; import { TableBodyType } from '../shared/Table';
import { Loading, Table, EmptyState, Error, Pagination, PageHeader, pageLimit } from '../shared'; import { Loading, Table, EmptyState, Error, Pagination, PageHeader, pageLimit } from '../shared';
import { useRouter } from '../hooks'; import { useRouter } from '../hooks';
import { useEffect } from 'react';
export const DirectoryUsers = ({ export const DirectoryUsers = ({
urls, urls,
@ -18,7 +19,7 @@ export const DirectoryUsers = ({
}) => { }) => {
const { router } = useRouter(); const { router } = useRouter();
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const { paginate, setPaginate, pageTokenMap } = usePaginate(router!); const { paginate, setPaginate, pageTokenMap, setPageTokenMap } = usePaginate(router!);
const params = { const params = {
pageOffset: paginate.offset, pageOffset: paginate.offset,
@ -26,6 +27,7 @@ export const DirectoryUsers = ({
}; };
// For DynamoDB // For DynamoDB
// Use the (next)pageToken mapped to the previous page offset to get the current page
if (paginate.offset > 0 && pageTokenMap[paginate.offset - pageLimit]) { if (paginate.offset > 0 && pageTokenMap[paginate.offset - pageLimit]) {
params['pageToken'] = pageTokenMap[paginate.offset - pageLimit]; params['pageToken'] = pageTokenMap[paginate.offset - pageLimit];
} }
@ -33,7 +35,15 @@ export const DirectoryUsers = ({
const getUrl = addQueryParamsToPath(urls.getUsers, params); const getUrl = addQueryParamsToPath(urls.getUsers, params);
const { directory, isLoadingDirectory, directoryError } = useDirectory(urls.getDirectory); const { directory, isLoadingDirectory, directoryError } = useDirectory(urls.getDirectory);
const { data, isLoading, error } = useSWR<{ data: User[] }>(getUrl, fetcher); const { data, isLoading, error } = useSWR<ApiSuccess<User[]>>(getUrl, fetcher);
const nextPageToken = data?.pageToken;
useEffect(() => {
if (nextPageToken) {
setPageTokenMap((tokenMap) => ({ ...tokenMap, [paginate.offset]: nextPageToken }));
}
}, [nextPageToken, paginate.offset]);
if (isLoading || isLoadingDirectory) { if (isLoading || isLoadingDirectory) {
return <Loading />; return <Loading />;

View File

@ -1,8 +1,8 @@
import useSWR from 'swr'; import useSWR from 'swr';
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import EyeIcon from '@heroicons/react/24/outline/EyeIcon'; import EyeIcon from '@heroicons/react/24/outline/EyeIcon';
import type { WebhookEventLog } from '../types'; import { ApiSuccess, type WebhookEventLog } from '../types';
import { fetcher, addQueryParamsToPath } from '../utils'; import { fetcher, addQueryParamsToPath } from '../utils';
import { DirectoryTab } from '../dsync'; import { DirectoryTab } from '../dsync';
import { usePaginate, useDirectory } from '../hooks'; import { usePaginate, useDirectory } from '../hooks';
@ -32,7 +32,7 @@ export const DirectoryWebhookLogs = ({
const { router } = useRouter(); const { router } = useRouter();
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const [delModalVisible, setDelModalVisible] = useState(false); const [delModalVisible, setDelModalVisible] = useState(false);
const { paginate, setPaginate, pageTokenMap } = usePaginate(router!); const { paginate, setPaginate, pageTokenMap, setPageTokenMap } = usePaginate(router!);
const params = { const params = {
pageOffset: paginate.offset, pageOffset: paginate.offset,
@ -40,6 +40,7 @@ export const DirectoryWebhookLogs = ({
}; };
// For DynamoDB // For DynamoDB
// Use the (next)pageToken mapped to the previous page offset to get the current page
if (paginate.offset > 0 && pageTokenMap[paginate.offset - pageLimit]) { if (paginate.offset > 0 && pageTokenMap[paginate.offset - pageLimit]) {
params['pageToken'] = pageTokenMap[paginate.offset - pageLimit]; params['pageToken'] = pageTokenMap[paginate.offset - pageLimit];
} }
@ -47,7 +48,15 @@ export const DirectoryWebhookLogs = ({
const getUrl = addQueryParamsToPath(urls.getEvents, params); const getUrl = addQueryParamsToPath(urls.getEvents, params);
const { directory, isLoadingDirectory, directoryError } = useDirectory(urls.getDirectory); const { directory, isLoadingDirectory, directoryError } = useDirectory(urls.getDirectory);
const { data, isLoading, error } = useSWR<{ data: WebhookEventLog[] }>(getUrl, fetcher); const { data, isLoading, error } = useSWR<ApiSuccess<WebhookEventLog[]>>(getUrl, fetcher);
const nextPageToken = data?.pageToken;
useEffect(() => {
if (nextPageToken) {
setPageTokenMap((tokenMap) => ({ ...tokenMap, [paginate.offset]: nextPageToken }));
}
}, [nextPageToken, paginate.offset]);
if (isLoading || isLoadingDirectory) { if (isLoading || isLoadingDirectory) {
return <Loading />; return <Loading />;

View File

@ -17,6 +17,7 @@ import { TableBodyType } from '../shared/Table';
import { pageLimit } from '../shared/Pagination'; import { pageLimit } from '../shared/Pagination';
import { usePaginate } from '../hooks'; import { usePaginate } from '../hooks';
import { useRouter } from '../hooks'; import { useRouter } from '../hooks';
import { useEffect } from 'react';
type ExcludeFields = keyof Pick<SAMLFederationApp, 'product'>; type ExcludeFields = keyof Pick<SAMLFederationApp, 'product'>;
@ -35,7 +36,7 @@ export const FederatedSAMLApps = ({
}) => { }) => {
const { router } = useRouter(); const { router } = useRouter();
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const { paginate, setPaginate, pageTokenMap } = usePaginate(router!); const { paginate, setPaginate, pageTokenMap, setPageTokenMap } = usePaginate(router!);
let getAppsUrl = `${urls.getApps}?pageOffset=${paginate.offset}&pageLimit=${pageLimit}`; let getAppsUrl = `${urls.getApps}?pageOffset=${paginate.offset}&pageLimit=${pageLimit}`;
@ -44,7 +45,18 @@ export const FederatedSAMLApps = ({
getAppsUrl += `&pageToken=${pageTokenMap[paginate.offset - pageLimit]}`; getAppsUrl += `&pageToken=${pageTokenMap[paginate.offset - pageLimit]}`;
} }
const { data, isLoading, error } = useSWR<{ data: SAMLFederationApp[] }>(getAppsUrl, fetcher); const { data, isLoading, error } = useSWR<{ data: SAMLFederationApp[]; pageToken?: string }>(
getAppsUrl,
fetcher
);
const nextPageToken = data?.pageToken;
useEffect(() => {
if (nextPageToken) {
setPageTokenMap((tokenMap) => ({ ...tokenMap, [paginate.offset]: nextPageToken }));
}
}, [nextPageToken, paginate.offset]);
if (isLoading) { if (isLoading) {
return <Loading />; return <Loading />;

View File

@ -1,5 +1,5 @@
import useSWR from 'swr'; import useSWR from 'swr';
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import EyeIcon from '@heroicons/react/24/outline/EyeIcon'; import EyeIcon from '@heroicons/react/24/outline/EyeIcon';
import TrashIcon from '@heroicons/react/24/outline/TrashIcon'; import TrashIcon from '@heroicons/react/24/outline/TrashIcon';
@ -51,7 +51,7 @@ export const SetupLinks = ({
const [showSetupLink, setShowSetupLink] = useState(false); const [showSetupLink, setShowSetupLink] = useState(false);
const [showRegenModal, setShowRegenModal] = useState(false); const [showRegenModal, setShowRegenModal] = useState(false);
const [setupLink, setSetupLink] = useState<SetupLink | null>(null); const [setupLink, setSetupLink] = useState<SetupLink | null>(null);
const { paginate, setPaginate, pageTokenMap } = usePaginate(router!); const { paginate, setPaginate, pageTokenMap, setPageTokenMap } = usePaginate(router!);
const params = { const params = {
pageOffset: paginate.offset, pageOffset: paginate.offset,
@ -65,7 +65,18 @@ export const SetupLinks = ({
} }
const getLinksUrl = addQueryParamsToPath(urls.getLinks, params); const getLinksUrl = addQueryParamsToPath(urls.getLinks, params);
const { data, isLoading, error, mutate } = useSWR<{ data: SetupLink[] }>(getLinksUrl, fetcher); const { data, isLoading, error, mutate } = useSWR<{ data: SetupLink[]; pageToken?: string }>(
getLinksUrl,
fetcher
);
const nextPageToken = data?.pageToken;
useEffect(() => {
if (nextPageToken) {
setPageTokenMap((tokenMap) => ({ ...tokenMap, [paginate.offset]: nextPageToken }));
}
}, [nextPageToken, paginate.offset]);
if (isLoading) { if (isLoading) {
return <Loading />; return <Loading />;

View File

@ -1,6 +1,13 @@
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import type { Group, DatabaseStore, PaginationParams, Response, GroupMembership } from '../../typings'; import type {
Group,
DatabaseStore,
PaginationParams,
Response,
GroupMembership,
Records,
} from '../../typings';
import * as dbutils from '../../db/utils'; import * as dbutils from '../../db/utils';
import { apiError, JacksonError } from '../../controller/error'; import { apiError, JacksonError } from '../../controller/error';
import { Base } from './Base'; import { Base } from './Base';
@ -238,10 +245,10 @@ export class Groups extends Base {
directoryId?: string; directoryId?: string;
} }
): Promise<Response<Group[]>> { ): Promise<Response<Group[]>> {
const { pageOffset, pageLimit, directoryId } = params; const { pageOffset, pageLimit, pageToken, directoryId } = params;
try { try {
let groups: Group[] = []; let result: Records;
// Filter by directoryId // Filter by directoryId
if (directoryId) { if (directoryId) {
@ -250,12 +257,12 @@ export class Groups extends Base {
value: directoryId, value: directoryId,
}; };
groups = (await this.store('groups').getByIndex(index, pageOffset, pageLimit)).data; result = await this.store('groups').getByIndex(index, pageOffset, pageLimit, pageToken);
} else { } else {
groups = (await this.store('groups').getAll(pageOffset, pageLimit)).data; result = await this.store('groups').getAll(pageOffset, pageLimit, pageToken);
} }
return { data: groups, error: null }; return { data: result.data, error: null, pageToken: result.pageToken };
} catch (err: any) { } catch (err: any) {
return apiError(err); return apiError(err);
} }

View File

@ -1,4 +1,4 @@
import type { User, DatabaseStore, PaginationParams, Response } from '../../typings'; import type { User, DatabaseStore, PaginationParams, Response, Records } from '../../typings';
import { apiError, JacksonError } from '../../controller/error'; import { apiError, JacksonError } from '../../controller/error';
import { Base } from './Base'; import { Base } from './Base';
import { keyFromParts } from '../../db/utils'; import { keyFromParts } from '../../db/utils';
@ -180,30 +180,29 @@ export class Users extends Base {
public async getAll({ public async getAll({
pageOffset, pageOffset,
pageLimit, pageLimit,
pageToken,
directoryId, directoryId,
}: PaginationParams & { }: PaginationParams & {
directoryId?: string; directoryId?: string;
} = {}): Promise<Response<User[]>> { } = {}): Promise<Response<User[]>> {
try { try {
let users: User[] = []; let result: Records;
// Filter by directoryId // Filter by directoryId
if (directoryId) { if (directoryId) {
users = ( result = await this.store('users').getByIndex(
await this.store('users').getByIndex( {
{ name: indexNames.directoryId,
name: indexNames.directoryId, value: directoryId,
value: directoryId, },
}, pageOffset,
pageOffset, pageLimit,
pageLimit pageToken
) );
).data as User[];
} else { } else {
users = (await this.store('users').getAll(pageOffset, pageLimit)).data; result = await this.store('users').getAll(pageOffset, pageLimit, pageToken);
} }
return { data: users, error: null }; return { data: result.data, error: null, pageToken: result.pageToken };
} catch (err: any) { } catch (err: any) {
return apiError(err); return apiError(err);
} }

View File

@ -6,6 +6,7 @@ import type {
WebhookEventLog, WebhookEventLog,
DirectorySyncEvent, DirectorySyncEvent,
PaginationParams, PaginationParams,
Records,
} from '../../typings'; } from '../../typings';
import { Base } from './Base'; import { Base } from './Base';
import { webhookLogsTTL } from '../utils'; import { webhookLogsTTL } from '../utils';
@ -125,9 +126,9 @@ export class WebhookEventsLogger extends Base {
*/ */
// Get the event logs for a directory paginated // Get the event logs for a directory paginated
public async getAll(params: GetAllParams = {}) { public async getAll(params: GetAllParams = {}) {
const { pageOffset, pageLimit, directoryId } = params; const { pageOffset, pageLimit, pageToken, directoryId } = params;
let eventLogs: WebhookEventLog[] = []; let result: Records<WebhookEventLog>;
if (directoryId) { if (directoryId) {
const index = { const index = {
@ -135,12 +136,12 @@ export class WebhookEventsLogger extends Base {
value: directoryId, value: directoryId,
}; };
eventLogs = (await this.eventStore().getByIndex(index, pageOffset, pageLimit)).data; result = await this.eventStore().getByIndex(index, pageOffset, pageLimit, pageToken);
} else { } else {
eventLogs = (await this.eventStore().getAll(pageOffset, pageLimit)).data; result = await this.eventStore().getAll(pageOffset, pageLimit, pageToken);
} }
return eventLogs; return { data: result.data, pageToken: result.pageToken };
} }
public async delete(id: string) { public async delete(id: string) {

View File

@ -178,7 +178,7 @@ export type GroupMembership = {
user_id: string; user_id: string;
}; };
export type Response<T> = { data: T; error: null } | { data: null; error: ApiError }; export type Response<T> = { data: T; error: null; pageToken?: string } | { data: null; error: ApiError };
export type EventCallback = (event: DirectorySyncEvent) => Promise<void>; export type EventCallback = (event: DirectorySyncEvent) => Promise<void>;

View File

@ -140,7 +140,7 @@ tap.test('Event batching', async (t) => {
}); });
t.test('Should log the webhook events if logging is enabled', async (t) => { t.test('Should log the webhook events if logging is enabled', async (t) => {
const logs = await directorySync.webhookLogs const { data: logs } = await directorySync.webhookLogs
.setTenantAndProduct(directory1Payload.tenant, directory1Payload.product) .setTenantAndProduct(directory1Payload.tenant, directory1Payload.product)
.getAll(); .getAll();
@ -157,7 +157,7 @@ tap.test('Event batching', async (t) => {
}); });
t.test('Should not log the webhook events if logging is disabled', async (t) => { t.test('Should not log the webhook events if logging is disabled', async (t) => {
const logs = await directorySync.webhookLogs const { data: logs } = await directorySync.webhookLogs
.setTenantAndProduct(directory2Payload.tenant, directory2Payload.product) .setTenantAndProduct(directory2Payload.tenant, directory2Payload.product)
.getAll(); .getAll();

View File

@ -78,7 +78,7 @@ tap.test('Webhook Events /', async (t) => {
// Create a user // Create a user
await directorySync.requests.handle(usersRequest.create(directory, users[0])); await directorySync.requests.handle(usersRequest.create(directory, users[0]));
const events = await directorySync.webhookLogs.getAll(); const { data: events } = await directorySync.webhookLogs.getAll();
t.equal(events.length, 0); t.equal(events.length, 0);
@ -100,7 +100,7 @@ tap.test('Webhook Events /', async (t) => {
// Create a user // Create a user
await directorySync.requests.handle(usersRequest.create(directory, users[0])); await directorySync.requests.handle(usersRequest.create(directory, users[0]));
const events = await directorySync.webhookLogs.getAll(); const { data: events } = await directorySync.webhookLogs.getAll();
t.equal(events.length, 0); t.equal(events.length, 0);
@ -114,7 +114,7 @@ tap.test('Webhook Events /', async (t) => {
// Create a user // Create a user
await directorySync.requests.handle(usersRequest.create(directory, users[0])); await directorySync.requests.handle(usersRequest.create(directory, users[0]));
const logs = await directorySync.webhookLogs.getAll(); const { data: logs } = await directorySync.webhookLogs.getAll();
const log = await directorySync.webhookLogs.get(logs[0].id); const log = await directorySync.webhookLogs.get(logs[0].id);
@ -144,7 +144,7 @@ tap.test('Webhook Events /', async (t) => {
mock.verify(); mock.verify();
mock.restore(); mock.restore();
const logs = await directorySync.webhookLogs.getAll(); const { data: logs } = await directorySync.webhookLogs.getAll();
t.ok(logs); t.ok(logs);
t.equal(logs.length, 3); t.equal(logs.length, 3);
@ -193,7 +193,7 @@ tap.test('Webhook Events /', async (t) => {
mock.verify(); mock.verify();
mock.restore(); mock.restore();
const logs = await directorySync.webhookLogs.getAll(); const { data: logs } = await directorySync.webhookLogs.getAll();
t.ok(logs); t.ok(logs);
t.equal(logs.length, 3); t.equal(logs.length, 3);
@ -251,7 +251,7 @@ tap.test('Webhook Events /', async (t) => {
mock.verify(); mock.verify();
mock.restore(); mock.restore();
const logs = await directorySync.webhookLogs.getAll(); const { data: logs } = await directorySync.webhookLogs.getAll();
t.ok(logs); t.ok(logs);
t.equal(logs.length, 4); t.equal(logs.length, 4);

View File

@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson'; import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api'; import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error'; import { ApiError } from '@lib/error';
import { parsePaginateApiParams } from '@lib/utils';
const handler = async (req: NextApiRequest, res: NextApiResponse) => { const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await defaultHandler(req, res, { await defaultHandler(req, res, {
@ -14,7 +15,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const handleGET = async (req: NextApiRequest, res: NextApiResponse) => { const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { directorySyncController } = await jackson(); const { directorySyncController } = await jackson();
const { directoryId, offset, limit } = req.query as { directoryId: string; offset: string; limit: string }; const { directoryId } = req.query as { directoryId: string };
const { pageOffset, pageLimit, pageToken } = parsePaginateApiParams(req.query);
const { data: directory, error } = await directorySyncController.directories.get(directoryId); const { data: directory, error } = await directorySyncController.directories.get(directoryId);
@ -22,18 +25,20 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
throw new ApiError(error.message, error.code); throw new ApiError(error.message, error.code);
} }
const pageOffset = parseInt(offset); const result = await directorySyncController.webhookLogs
const pageLimit = parseInt(limit);
const events = await directorySyncController.webhookLogs
.setTenantAndProduct(directory.tenant, directory.product) .setTenantAndProduct(directory.tenant, directory.product)
.getAll({ .getAll({
pageOffset, pageOffset,
pageLimit, pageLimit,
pageToken,
directoryId, directoryId,
}); });
res.json({ data: events }); if (result.pageToken) {
res.setHeader('jackson-pagetoken', result.pageToken);
}
res.json({ data: result.data });
}; };
// Delete all events // Delete all events

View File

@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson'; import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api'; import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error'; import { ApiError } from '@lib/error';
import { parsePaginateApiParams } from '@lib/utils';
const handler = async (req: NextApiRequest, res: NextApiResponse) => { const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await defaultHandler(req, res, { await defaultHandler(req, res, {
@ -13,7 +14,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const handleGET = async (req: NextApiRequest, res: NextApiResponse) => { const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { directorySyncController } = await jackson(); const { directorySyncController } = await jackson();
const { directoryId, offset, limit } = req.query as { directoryId: string; offset: string; limit: string }; const { directoryId } = req.query as { directoryId: string };
const { pageOffset, pageLimit, pageToken } = parsePaginateApiParams(req.query);
const { data: directory, error } = await directorySyncController.directories.get(directoryId); const { data: directory, error } = await directorySyncController.directories.get(directoryId);
@ -21,18 +24,17 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
throw new ApiError(error.message, error.code); throw new ApiError(error.message, error.code);
} }
const pageOffset = parseInt(offset); const result = await directorySyncController.groups
const pageLimit = parseInt(limit);
const { data: groups, error: groupsError } = await directorySyncController.groups
.setTenantAndProduct(directory.tenant, directory.product) .setTenantAndProduct(directory.tenant, directory.product)
.getAll({ pageOffset, pageLimit, directoryId }); .getAll({ pageOffset, pageLimit, pageToken, directoryId });
if (groupsError) { if (result.error) {
throw new ApiError(groupsError.message, groupsError.code); throw new ApiError(result.error.message, result.error.code);
} else if (result.pageToken) {
res.setHeader('jackson-pagetoken', result.pageToken);
} }
res.json({ data: groups }); res.json({ data: result.data });
}; };
export default handler; export default handler;

View File

@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import jackson from '@lib/jackson'; import jackson from '@lib/jackson';
import { defaultHandler } from '@lib/api'; import { defaultHandler } from '@lib/api';
import { ApiError } from '@lib/error'; import { ApiError } from '@lib/error';
import { parsePaginateApiParams } from '@lib/utils';
const handler = async (req: NextApiRequest, res: NextApiResponse) => { const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await defaultHandler(req, res, { await defaultHandler(req, res, {
@ -13,7 +14,11 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const handleGET = async (req: NextApiRequest, res: NextApiResponse) => { const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { directorySyncController } = await jackson(); const { directorySyncController } = await jackson();
const { directoryId, offset, limit } = req.query as { directoryId: string; offset: string; limit: string }; const { directoryId } = req.query as {
directoryId: string;
};
const { pageOffset, pageLimit, pageToken } = parsePaginateApiParams(req.query);
const { data: directory, error } = await directorySyncController.directories.get(directoryId); const { data: directory, error } = await directorySyncController.directories.get(directoryId);
@ -21,18 +26,17 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
throw new ApiError(error.message, error.code); throw new ApiError(error.message, error.code);
} }
const pageOffset = parseInt(offset); const result = await directorySyncController.users
const pageLimit = parseInt(limit);
const { data: users, error: usersError } = await directorySyncController.users
.setTenantAndProduct(directory.tenant, directory.product) .setTenantAndProduct(directory.tenant, directory.product)
.getAll({ pageOffset, pageLimit, directoryId }); .getAll({ pageOffset, pageLimit, pageToken, directoryId });
if (usersError) { if (result.error) {
throw new ApiError(usersError.message, usersError.code); throw new ApiError(result.error.message, result.error.code);
} else if (result.pageToken) {
res.setHeader('jackson-pagetoken', result.pageToken);
} }
res.json({ data: users }); res.json({ data: result.data });
}; };
export default handler; export default handler;