Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 48 additions & 19 deletions connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions connect/src/wg/cosmo/platform/v1/platform_pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13892,6 +13892,16 @@ export class GetAllOverridesRequest extends Message<GetAllOverridesRequest> {
*/
namespace = "";

/**
* @generated from field: int32 limit = 3;
*/
limit = 0;

/**
* @generated from field: int32 offset = 4;
*/
offset = 0;

constructor(data?: PartialMessage<GetAllOverridesRequest>) {
super();
proto3.util.initPartial(data, this);
Expand All @@ -13902,6 +13912,8 @@ export class GetAllOverridesRequest extends Message<GetAllOverridesRequest> {
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "graph_name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 2, name: "namespace", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 3, name: "limit", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
{ no: 4, name: "offset", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
]);

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetAllOverridesRequest {
Expand Down Expand Up @@ -13935,6 +13947,11 @@ export class GetAllOverridesResponse extends Message<GetAllOverridesResponse> {
*/
overrides: GetAllOverridesResponse_Override[] = [];

/**
* @generated from field: int32 total_count = 3;
*/
totalCount = 0;

constructor(data?: PartialMessage<GetAllOverridesResponse>) {
super();
proto3.util.initPartial(data, this);
Expand All @@ -13945,6 +13962,7 @@ export class GetAllOverridesResponse extends Message<GetAllOverridesResponse> {
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "response", kind: "message", T: Response },
{ no: 2, name: "overrides", kind: "message", T: GetAllOverridesResponse_Override, repeated: true },
{ no: 3, name: "total_count", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
]);

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetAllOverridesResponse {
Expand Down
10 changes: 8 additions & 2 deletions controlplane/src/core/bufservices/check/getAllOverrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { FederatedGraphRepository } from '../../repositories/FederatedGraphRepository.js';
import { OperationsRepository } from '../../repositories/OperationsRepository.js';
import type { RouterOptions } from '../../routes.js';
import { enrichLogger, getLogger, handleError } from '../../util.js';
import { enrichLogger, getLogger, handleError, normalizePagination } from '../../util.js';
import { UnauthorizedError } from '../../errors/errors.js';

export function getAllOverrides(
Expand All @@ -33,6 +33,7 @@ export function getAllOverrides(
details: 'Requested graph does not exist',
},
overrides: [],
totalCount: 0,
};
}

Expand All @@ -42,15 +43,20 @@ export function getAllOverrides(

const operationsRepo = new OperationsRepository(opts.db, graph.id);

const overrides = await operationsRepo.getConsolidatedOverridesView({
const { limit, offset } = normalizePagination({ limit: req.limit, offset: req.offset });

const { overrides, totalCount } = await operationsRepo.getConsolidatedOverridesView({
namespaceId: graph.namespaceId,
limit,
offset,
});

return {
response: {
code: EnumStatusCode.OK,
},
overrides,
totalCount,
};
});
}
9 changes: 9 additions & 0 deletions controlplane/src/core/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ export const hubUserAgent = 'cosmo-hub';

export const maxRowLimitForChecks = 100_000;

// Pagination defaults used by server-side paginated endpoints
export const paginationDefaults = {
defaultLimit: 10,
minLimit: 1,
maxLimit: 50,
minOffset: 0,
maxOffset: 500_000,
} as const;

export const apiKeyPermissions = [
{
displayName: 'System for Cross-domain Identity Management (SCIM)',
Expand Down
24 changes: 20 additions & 4 deletions controlplane/src/core/repositories/OperationsRepository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { OverrideChange } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
import { aliasedTable, and, asc, desc, eq, isNull, sql } from 'drizzle-orm';
import { aliasedTable, and, asc, count, desc, eq, isNull, sql } from 'drizzle-orm';
import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
import { PlainMessage } from '@bufbuild/protobuf';
import { DBSchemaChangeType } from '../../db/models.js';
Expand Down Expand Up @@ -443,7 +443,7 @@ export class OperationsRepository {
};
}

public getConsolidatedOverridesView(data: { namespaceId: string }) {
public async getConsolidatedOverridesView(data: { namespaceId: string; limit: number; offset: number }) {
const change = this.db
.select({
hash: schema.operationChangeOverrides.hash,
Expand Down Expand Up @@ -484,7 +484,7 @@ export class OperationsRepository {
// We need to retrieve a consolidated view of overrides from both tables.
// There is no guarantee that an entry for hash exists in both.

return this.db
const baseQuery = this.db
.select({
hash: sql<string>`coalesce(${change.hash}, ${ignore.hash})`,
name: sql<string>`coalesce(${change.name}, ${ignore.name})`,
Expand All @@ -497,7 +497,23 @@ export class OperationsRepository {
.from(change)
.fullJoin(ignore, and(eq(change.hash, ignore.hash), eq(change.namespaceId, ignore.namespaceId)))
.leftJoin(changeCounts, and(eq(change.hash, changeCounts.hash), eq(change.namespaceId, changeCounts.namespaceId)))
.orderBy(({ name, hash }) => [asc(name), asc(hash)]);
.orderBy(({ name, hash }) => [asc(name), asc(hash)])
.limit(data.limit)
.offset(data.offset);

const countQuery = this.db
.select({
count: count(),
})
.from(change)
.fullJoin(ignore, and(eq(change.hash, ignore.hash), eq(change.namespaceId, ignore.namespaceId)));

const [overrides, countResult] = await Promise.all([baseQuery, countQuery]);

return {
overrides,
totalCount: countResult[0]?.count ?? 0,
};
}

private static createPersistedOperationDTO({
Expand Down
32 changes: 32 additions & 0 deletions controlplane/src/core/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
isValidGrpcNamingScheme,
isValidLabels,
isValidNamespaceName,
normalizePagination,
} from './util.js';
import { organizationSlugSchema } from './constants.js';

Expand Down Expand Up @@ -491,4 +492,35 @@ describe('isValidGrpcNamingScheme', () => {
expect(isValidGrpcNamingScheme('invalid:')).toBe(false);
});
});

describe('normalizePagination', () => {
test('should apply defaults when no values are provided', () => {
const result = normalizePagination({});
expect(result).toEqual({ limit: 10, offset: 0 });
});

test('should pass through valid values', () => {
const result = normalizePagination({ limit: 25, offset: 100 });
expect(result).toEqual({ limit: 25, offset: 100 });
});

test('should clamp limit to min/max bounds', () => {
expect(normalizePagination({ limit: 0 }).limit).toBe(10); // falsy → default
expect(normalizePagination({ limit: 100 }).limit).toBe(50); // above max
});

test('should clamp offset to max bound', () => {
expect(normalizePagination({ offset: 600_000 }).offset).toBe(500_000);
});

test('should respect custom maxLimit override', () => {
const result = normalizePagination({ limit: 300 }, { maxLimit: 200 });
expect(result.limit).toBe(200);
});

test('should respect custom maxOffset override', () => {
const result = normalizePagination({ offset: 2000 }, { maxOffset: 1000 });
expect(result.offset).toBe(1000);
});
});
});
18 changes: 18 additions & 0 deletions controlplane/src/core/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { LATEST_ROUTER_COMPATIBILITY_VERSION } from '@wundergraph/composition';
import { ProposalOrigin, SubgraphType } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
import { MemberRole, ProposalOrigin as ProposalOriginEnum, WebsocketSubprotocol } from '../db/models.js';
import { AuthContext, DateRange, FederatedGraphDTO, Label, ResponseMessage, S3StorageOptions } from '../types/index.js';
import { paginationDefaults } from './constants.js';
import { isAuthenticationError, isAuthorizationError, isPublicError } from './errors/errors.js';
import { GraphKeyAuthContext } from './services/GraphApiTokenAuthenticator.js';

Expand Down Expand Up @@ -545,6 +546,23 @@ export function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}

/**
* Normalizes pagination parameters by applying defaults and clamping to safe bounds.
* Uses the standard pagination defaults from constants unless overridden.
*/
export function normalizePagination(
opts: { limit?: number; offset?: number },
overrides?: { maxLimit?: number; maxOffset?: number },
): { limit: number; offset: number } {
const maxLimit = overrides?.maxLimit ?? paginationDefaults.maxLimit;
const maxOffset = overrides?.maxOffset ?? paginationDefaults.maxOffset;

return {
limit: clamp(opts.limit || paginationDefaults.defaultLimit, paginationDefaults.minLimit, maxLimit),
offset: clamp(opts.offset || 0, paginationDefaults.minOffset, maxOffset),
};
}

export const isCheckSuccessful = ({
isComposable,
isBreaking,
Expand Down
Loading
Loading