Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
0d752bc
feat(SITES-44690): add TaskManagementConnection and Ticket data models
prithipalpatwal Jun 22, 2026
1670b27
docs(data-models): clarify STATUSES.DISCONNECTED v1 scope intent
prithipalpatwal Jun 22, 2026
ed28a09
test(data-models): fix electrodb timeout and add TaskManagementConnec…
prithipalpatwal Jun 22, 2026
470893a
Merge branch 'main' into feat/SITES-44690-data-models
prithipalpatwal Jun 22, 2026
9759dd1
feat(data-models): add ticketProvider and createdBy to Ticket schema
prithipalpatwal Jun 22, 2026
fd9d00f
feat(data-models): add TicketSuggestion bridge model
prithipalpatwal Jun 22, 2026
956066e
fix(data-models): add disabled, error statuses + markDisabled/markErr…
prithipalpatwal Jun 23, 2026
c259842
feat(data-models): add metadata validation + export validateMetadata
prithipalpatwal Jun 23, 2026
f16ed27
Merge branch 'main' into feat/SITES-44690-data-models
prithipalpatwal Jun 24, 2026
7066e87
fix(data-models): align task-management models with PR #720 and solut…
prithipalpatwal Jun 24, 2026
1eadf94
fix(data-models): correct stale metadata comment in TaskManagementCon…
prithipalpatwal Jun 24, 2026
9b00cf5
Merge branch 'main' into feat/SITES-44690-data-models
prithipalpatwal Jun 24, 2026
93ee359
feat(data-models): add externalInstanceId to TaskManagementConnection…
prithipalpatwal Jun 24, 2026
e647141
Merge remote-tracking branch 'origin/feat/SITES-44690-data-models' in…
prithipalpatwal Jun 24, 2026
ee44973
feat(data-models): add OAuthNonce model and fix schema correctness is…
prithipalpatwal Jun 24, 2026
8a8f0f2
fix(data-models): address PR #1702 review blockers (SITES-44690)
prithipalpatwal Jun 24, 2026
15613d5
fix(data-models): remove unreachable jira_corp metadata schema (SITES…
prithipalpatwal Jun 24, 2026
33e771e
Merge remote-tracking branch 'origin/main' into feat/SITES-44690-data…
prithipalpatwal Jun 24, 2026
c06aac4
fix(data-models): add lastUsedAt and errorMessage attributes to TaskM…
prithipalpatwal Jun 25, 2026
63b0b73
fix(data-models): add missing TypeScript declarations for task-manage…
prithipalpatwal Jun 25, 2026
c6d15cf
fix(data-access): rename ticketId to externalTicketId, add schema tes…
prithipalpatwal Jun 25, 2026
cb77699
Merge branch 'main' into feat/SITES-44690-data-models
prithipalpatwal Jun 25, 2026
3a5cbc2
fix(data-access): bump IT data-service image to v5.48.1
prithipalpatwal Jun 25, 2026
a331cc4
fix(data-access): guard validateMetadata and OAuthNonce.delete agains…
prithipalpatwal Jun 26, 2026
7be1031
feat(data-access): add connectedAt attribute to TaskManagementConnection
prithipalpatwal Jun 26, 2026
e2c95cb
fix(data-access): use isIsoDate, wire metadata validation, fix copyri…
prithipalpatwal Jun 26, 2026
1d86ea2
Merge branch 'main' into feat/SITES-44690-data-models
prithipalpatwal Jul 1, 2026
332d9c5
fix(data-access): correct TicketSuggestion index test to use Object.v…
prithipalpatwal Jul 1, 2026
eca5cb3
test(data-access): cover null/array metadata branches in metadata-val…
prithipalpatwal Jul 1, 2026
ad35864
test(data-access): cover missing-nonce guard in OAuthNonceCollection.…
prithipalpatwal Jul 1, 2026
623841d
fix(data-access): remove readOnly from displayName and instanceUrl on…
prithipalpatwal Jul 1, 2026
84c7399
fix(data-access): map Ticket FK to connection_id column and add TypeS…
prithipalpatwal Jul 1, 2026
176dd8c
fix(data-access): suppress updatedAt/updatedBy for entities without t…
prithipalpatwal Jul 1, 2026
86f44bb
fix(data-access): mark OAuthNonce and TicketSuggestion as allowUpdate…
prithipalpatwal Jul 1, 2026
7e6fa58
fix(data-access): fix TypeScript declarations and schema comment for …
prithipalpatwal Jul 1, 2026
15d0e43
fix(data-access): enforce expiry check in OAuthNonce.delete() (SITES-…
prithipalpatwal Jul 1, 2026
787ea52
Merge branch 'main' into feat/SITES-44690-data-models
prithipalpatwal Jul 2, 2026
1702148
feat(data-access): add IdempotencyKey data model (SITES-44690)
prithipalpatwal Jul 2, 2026
2a5e0dc
fix(data-access): wrap IdempotencyKey PostgREST errors in DataAccessE…
prithipalpatwal Jul 2, 2026
e54e75f
fix(data-access): mark IdempotencyKey.expiresAt as readOnly and tight…
prithipalpatwal Jul 2, 2026
3c3dcc5
Merge branch 'main' into feat/SITES-44690-data-models
prithipalpatwal Jul 2, 2026
b772da1
Merge branch 'main' into feat/SITES-44690-data-models
prithipalpatwal Jul 2, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ import SentimentGuidelineCollection from '../sentiment-guideline/sentiment-guide
import SentimentTopicCollection from '../sentiment-topic/sentiment-topic.collection.js';
import AccessGrantLogCollection from '../access-grant-log/access-grant-log.collection.js';
import SiteImsOrgAccessCollection from '../site-ims-org-access/site-ims-org-access.collection.js';
import IdempotencyKeyCollection from '../idempotency-key/idempotency-key.collection.js';
import OAuthNonceCollection from '../oauth-nonce/oauth-nonce.collection.js';
import TaskManagementConnectionCollection from '../task-management-connection/task-management-connection.collection.js';
import TicketCollection from '../ticket/ticket.collection.js';
import TicketSuggestionCollection from '../ticket-suggestion/ticket-suggestion.collection.js';

import ApiKeySchema from '../api-key/api-key.schema.js';
import AsyncJobSchema from '../async-job/async-job.schema.js';
Expand Down Expand Up @@ -97,6 +102,11 @@ import SentimentGuidelineSchema from '../sentiment-guideline/sentiment-guideline
import SentimentTopicSchema from '../sentiment-topic/sentiment-topic.schema.js';
import AccessGrantLogSchema from '../access-grant-log/access-grant-log.schema.js';
import SiteImsOrgAccessSchema from '../site-ims-org-access/site-ims-org-access.schema.js';
import IdempotencyKeySchema from '../idempotency-key/idempotency-key.schema.js';
import OAuthNonceSchema from '../oauth-nonce/oauth-nonce.schema.js';
import TaskManagementConnectionSchema from '../task-management-connection/task-management-connection.schema.js';
import TicketSchema from '../ticket/ticket.schema.js';
import TicketSuggestionSchema from '../ticket-suggestion/ticket-suggestion.schema.js';

/**
* EntityRegistry - A registry class responsible for managing entities, their schema and collection.
Expand Down Expand Up @@ -234,6 +244,11 @@ EntityRegistry.registerEntity(SentimentGuidelineSchema, SentimentGuidelineCollec
EntityRegistry.registerEntity(SentimentTopicSchema, SentimentTopicCollection);
EntityRegistry.registerEntity(AccessGrantLogSchema, AccessGrantLogCollection);
EntityRegistry.registerEntity(SiteImsOrgAccessSchema, SiteImsOrgAccessCollection);
EntityRegistry.registerEntity(IdempotencyKeySchema, IdempotencyKeyCollection);
EntityRegistry.registerEntity(OAuthNonceSchema, OAuthNonceCollection);
EntityRegistry.registerEntity(TaskManagementConnectionSchema, TaskManagementConnectionCollection);
EntityRegistry.registerEntity(TicketSchema, TicketCollection);
EntityRegistry.registerEntity(TicketSuggestionSchema, TicketSuggestionCollection);
EntityRegistry.defaultEntities = { ...EntityRegistry.entities };

export default EntityRegistry;
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { hasText, isValidUUID } from '@adobe/spacecat-shared-utils';

import { DataAccessError, ValidationError } from '../../errors/index.js';
import BaseCollection from '../base/base.collection.js';

/**
* IdempotencyKeyCollection — manages IdempotencyKey entities.
*
* Auto-generated index query methods (via schema references):
* allByOrganizationId(organizationId) — all keys for an organization
*
* @class IdempotencyKeyCollection
* @extends BaseCollection
*/
class IdempotencyKeyCollection extends BaseCollection {
static COLLECTION_NAME = 'IdempotencyKeyCollection';

/**
* Finds a non-expired idempotency key by its value and organization.
*
* Returns null when:
* - No key exists with that value for the organization
* - The key exists but has expired (expires_at < NOW())
*
* @param {string} key - The idempotency key value.
* @param {string} organizationId - The organization UUID.
* @returns {Promise<import('./idempotency-key.model.js').default|null>}
*/
async findActiveKey(key, organizationId) {
if (!hasText(key)) {
throw new ValidationError('key is required', this);
}
if (!isValidUUID(organizationId)) {
throw new ValidationError('organizationId must be a valid UUID', this);
}

const { data, error } = await this.postgrestService
.from(this.tableName)
.select()
.eq('key', key)
.eq('organization_id', organizationId)
.gt('expires_at', new Date().toISOString())
.limit(1);

if (error) {
throw new DataAccessError('Failed to find active idempotency key', { entityName: 'IdempotencyKey' }, error);
}

if (!data || data.length === 0) {
return null;
}

return this.createInstanceFromRow(data[0]);
}

/**
* Deletes all expired idempotency keys.
* Called by the cleanup scheduler (every 5 minutes).
*
* @returns {Promise<number>} Number of expired keys deleted.
*/
async deleteExpired() {
const { data, error } = await this.postgrestService
.from(this.tableName)
.delete()
.lt('expires_at', new Date().toISOString())
.select('id');

if (error) {
throw new DataAccessError('Failed to delete expired idempotency keys', { entityName: 'IdempotencyKey' }, error);
}

return (data ?? []).length;
}
}

export default IdempotencyKeyCollection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import BaseModel from '../base/base.model.js';

/**
* IdempotencyKey — deduplicates concurrent or retried requests using the
* Stripe idempotency pattern.
*
* Status lifecycle:
* processing → completed (cached success response)
* processing → failed (cached error response, client must generate new key to retry)
*
* Keys are scoped to (key, organizationId, endpoint) — cross-org collision is impossible
* and the same key can be used independently across different operations.
*
* Keys expire after 24 hours (or shorter for ephemeral locks like token refresh dedup).
*
* @class IdempotencyKey
* @extends BaseModel
*/
class IdempotencyKey extends BaseModel {
static ENTITY_NAME = 'IdempotencyKey';

static STATUSES = {
PROCESSING: 'processing',
COMPLETED: 'completed',
FAILED: 'failed',
};
}

export default IdempotencyKey;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/* c8 ignore start */

import { isIsoDate } from '@adobe/spacecat-shared-utils';

import SchemaBuilder from '../base/schema.builder.js';
import IdempotencyKey from './idempotency-key.model.js';
import IdempotencyKeyCollection from './idempotency-key.collection.js';

// idempotency_keys table has updated_at but no updated_by column.
const schema = new SchemaBuilder(IdempotencyKey, IdempotencyKeyCollection)
.addAttribute('updatedBy', { type: 'string', required: false, postgrestIgnore: true })
.addReference('belongs_to', 'Organization')
.addAttribute('key', {
type: 'string',
required: true,
readOnly: true,
})
.addAttribute('endpoint', {
type: 'string',
required: true,
readOnly: true,
})
.addAttribute('status', {
type: Object.values(IdempotencyKey.STATUSES),
required: true,
default: IdempotencyKey.STATUSES.PROCESSING,
})
.addAttribute('response', {
type: 'any',
required: false,
})
.addAttribute('expiresAt', {
type: 'string',
required: true,
readOnly: true,
validate: (value) => isIsoDate(value),
});

export default schema.build();
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import type { BaseCollection, BaseModel, Organization } from '../index';

export interface IdempotencyKey extends BaseModel {
getEndpoint(): string;
getExpiresAt(): string;
getKey(): string;
getOrganization(): Promise<Organization>;
getOrganizationId(): string;
getResponse(): Record<string, unknown> | null;
getStatus(): string;

setResponse(response: Record<string, unknown> | null): IdempotencyKey;
setStatus(status: string): IdempotencyKey;
}

export interface IdempotencyKeyCollection extends BaseCollection<IdempotencyKey> {
allByOrganizationId(organizationId: string): Promise<IdempotencyKey[]>;

/**
* Finds a non-expired idempotency key by its value and organization.
* Returns null when no active key exists.
*/
findActiveKey(key: string, organizationId: string): Promise<IdempotencyKey | null>;

/**
* Deletes all expired idempotency keys. Returns the number deleted.
*/
deleteExpired(): Promise<number>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import IdempotencyKey from './idempotency-key.model.js';
import IdempotencyKeyCollection from './idempotency-key.collection.js';

export {
IdempotencyKey,
IdempotencyKeyCollection,
};
5 changes: 5 additions & 0 deletions packages/spacecat-shared-data-access/src/models/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export type * from './import-url';
export type * from './key-event';
export type * from './latest-audit';
export type * from './opportunity';
export type * from './idempotency-key';
export type * from './oauth-nonce';
export type * from './organization';
export type * from './page-citability';
export type * from './page-intent';
Expand All @@ -40,6 +42,9 @@ export type * from './scrape-job';
export type * from './scrape-url';
export type * from './sentiment-guideline';
export type * from './sentiment-topic';
export type * from './task-management-connection';
export type * from './ticket';
export type * from './ticket-suggestion';
export type * from './site';
export type * from './site-candidate';
export type * from './site-enrollment';
Expand Down
5 changes: 5 additions & 0 deletions packages/spacecat-shared-data-access/src/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,8 @@ export * from './page-citability/index.js';
export * from './plg-onboarding/index.js';
export * from './sentiment-guideline/index.js';
export * from './sentiment-topic/index.js';
export * from './idempotency-key/index.js';
export * from './oauth-nonce/index.js';
export * from './task-management-connection/index.js';
export * from './ticket/index.js';
export * from './ticket-suggestion/index.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import type { BaseCollection, BaseModel } from '../index';

export interface OAuthNonce extends BaseModel {
getNonce(): string;
getExpiresAt(): string;
}

export interface OAuthNonceCollection extends BaseCollection<OAuthNonce> {
/**
* Atomically deletes a nonce by its value.
* Returns the number of rows deleted (1 = consumed, 0 = not found or already consumed).
*/
delete(keys: { nonce: string }): Promise<number>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import OAuthNonce from './oauth-nonce.model.js';
import OAuthNonceCollection from './oauth-nonce.collection.js';

export {
OAuthNonce,
OAuthNonceCollection,
};
Loading
Loading