Skip to content

Commit

Permalink
feat: Get a basic CRUD controller setup for package versions
Browse files Browse the repository at this point in the history
  • Loading branch information
KallynGowdy committed Nov 14, 2024
1 parent 83ba6ea commit e4371c8
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SubscriptionFilter } from '../../MetricsStore';
import { MemoryStore } from '../../MemoryStore';
import {
CrudResult,
GetSubCrudItemResult,
ListSubCrudStoreSuccess,
SubCrudRecord,
Expand Down Expand Up @@ -36,7 +37,20 @@ export class MemorySubCrudRecordsStore<
return item.key;
}

async createItem(recordName: string, item: T): Promise<void> {
async createItem(recordName: string, item: T): Promise<CrudResult> {
const recordItem = this._itemStore.getItemByAddress(
recordName,
item.address
);

if (!recordItem) {
return {
success: false,
errorCode: 'parent_not_found',
errorMessage: 'The parent item was not found.',
};
}

let bucket = this._itemBuckets.get(recordName);
if (!bucket) {
bucket = new Map();
Expand All @@ -53,6 +67,10 @@ export class MemorySubCrudRecordsStore<
if (index < 0) {
arr.push(item);
}

return {
success: true,
};
}

async getItemByKey(
Expand All @@ -61,49 +79,34 @@ export class MemorySubCrudRecordsStore<
key: TKey
): Promise<GetSubCrudItemResult<T>> {
const bucket = this._itemBuckets.get(recordName);
if (!bucket) {
return {
item: null,
markers: [],
};
}

const arr = bucket.get(address);
if (!arr) {
return {
item: null,
markers: [],
};
}

const item = arr.find((i) => isEqual(this.getKey(i), key)) ?? null;

if (!item) {
return {
item: null,
markers: [],
};
}

const arr = bucket?.get(address);
const item = arr?.find((i) => isEqual(this.getKey(i), key)) ?? null;
const recordItem = await this._itemStore.getItemByAddress(
recordName,
address
);

return {
item,
markers: recordItem?.markers ?? [],
markers: recordItem?.markers ?? null,
};
}

async updateItem(recordName: string, item: Partial<T>): Promise<void> {
async updateItem(
recordName: string,
item: Partial<T>
): Promise<CrudResult> {
const existing = await this.getItemByKey(
recordName,
item.address,
item as unknown as TKey
);
if (!existing.item) {
return;
return {
success: false,
errorCode: 'item_not_found',
errorMessage: 'Item not found',
};
}

const updated = {
Expand All @@ -124,9 +127,13 @@ export class MemorySubCrudRecordsStore<
} else {
// Do nothing if the item does not exist.
}

return {
success: true,
};
}

async putItem(recordName: string, item: Partial<T>): Promise<void> {
async putItem(recordName: string, item: Partial<T>): Promise<CrudResult> {
const existing = await this.getItemByKey(
recordName,
item.address,
Expand All @@ -138,13 +145,17 @@ export class MemorySubCrudRecordsStore<
}

await this.updateItem(recordName, item);

return {
success: true,
};
}

async deleteItem(
recordName: string,
address: string,
key: TKey
): Promise<void> {
): Promise<CrudResult> {
const bucket = this._itemBuckets.get(recordName);
if (!bucket) {
return;
Expand All @@ -159,6 +170,10 @@ export class MemorySubCrudRecordsStore<
if (index >= 0) {
arr.splice(index, 1);
}

return {
success: true,
};
}

async listItems(
Expand Down
2 changes: 1 addition & 1 deletion src/aux-records/crud/sub/SubCrudRecordsController.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
CrudRecordsConfiguration,
CrudRecordsController,
} from '../CrudRecordsController';
import { MemorySubCrudRecordsStore } from './SubMemoryCrudRecordsStore';
import { MemorySubCrudRecordsStore } from './MemorySubCrudRecordsStore';
import {
ActionKinds,
PRIVATE_MARKER,
Expand Down
9 changes: 9 additions & 0 deletions src/aux-records/crud/sub/SubCrudRecordsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,15 @@ export abstract class SubCrudRecordsController<
request.item.address,
request.item.key
);

if (!existingItem.markers) {
return {
success: false,
errorCode: 'data_not_found',
errorMessage: 'The parent item was not found.',
};
}

const resourceMarkers = existingItem.markers;

let action = existingItem.item
Expand Down
36 changes: 31 additions & 5 deletions src/aux-records/crud/sub/SubCrudRecordsControllerTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,32 @@ export function testCrudRecordsController<
});
});

it('should return data_not_found if the record item doesnt exist', async () => {
const item = createTestItem({
address: 'missing',
key: createKey(0),
});
const result = (await manager.recordItem({
recordKeyOrRecordName: recordName,
userId,
item,
instances: [],
})) as CrudRecordItemSuccess;

expect(result).toEqual({
success: false,
errorCode: 'data_not_found',
errorMessage: expect.any(String),
});

await expect(
itemsStore.getItemByKey(recordName, 'missing', createKey(0))
).resolves.toMatchObject({
item: null,
markers: null,
});
});

it('should reject the request if given an invalid key', async () => {
const result = (await manager.recordItem({
recordKeyOrRecordName: 'not_a_key',
Expand All @@ -287,7 +313,7 @@ export function testCrudRecordsController<
itemsStore.getItemByKey(recordName, 'address', createKey(0))
).resolves.toMatchObject({
item: null,
markers: [],
markers: [PUBLIC_READ_MARKER],
});
});

Expand Down Expand Up @@ -390,7 +416,7 @@ export function testCrudRecordsController<
)
).resolves.toMatchObject({
item: null,
markers: [],
markers: [PUBLIC_READ_MARKER],
});
});

Expand Down Expand Up @@ -428,7 +454,7 @@ export function testCrudRecordsController<
)
).resolves.toMatchObject({
item: null,
markers: [],
markers: [PUBLIC_READ_MARKER],
});
});
}
Expand Down Expand Up @@ -840,7 +866,7 @@ export function testCrudRecordsController<
itemsStore.getItemByKey(recordName, 'address2', createKey(2))
).resolves.toMatchObject({
item: null,
markers: [],
markers: [PRIVATE_MARKER],
});
});

Expand Down Expand Up @@ -898,7 +924,7 @@ export function testCrudRecordsController<
)
).resolves.toMatchObject({
item: null,
markers: [],
markers: [PRIVATE_MARKER],
});
});
} else {
Expand Down
27 changes: 23 additions & 4 deletions src/aux-records/crud/sub/SubCrudRecordsStore.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { KnownErrorCodes } from '@casual-simulation/aux-common';

/**
* Defines an interface for a store that can be used to create, read, update, and delete sub-items in a record.
* That is, items which are related to a parent resource kind. (subscription to notification, package version to package, etc.)
Expand All @@ -11,7 +13,7 @@ export interface SubCrudRecordsStore<TKey, T extends SubCrudRecord<TKey>> {
* @param recordName The name of the record.
* @param item The item to create.
*/
createItem(recordName: string, item: T): Promise<void>;
createItem(recordName: string, item: T): Promise<CrudResult>;

/**
* Reads the item with the given address. Always returns an object with the item and any markers that are related to the item.
Expand All @@ -31,23 +33,27 @@ export interface SubCrudRecordsStore<TKey, T extends SubCrudRecord<TKey>> {
* @param recordName The name of the record that the item lives in.
* @param record The record to update.
*/
updateItem(recordName: string, item: Partial<T>): Promise<void>;
updateItem(recordName: string, item: Partial<T>): Promise<CrudResult>;

/**
* Creates or updates the record with the given ID.
* If updating a record, keys that are not present in the record will not be updated.
* @param recordName The name of the record that the item lives in.
* @param item The item to create or update.
*/
putItem(recordName: string, item: Partial<T>): Promise<void>;
putItem(recordName: string, item: Partial<T>): Promise<CrudResult>;

/**
* Deletes the item with the given key.
* @param recordName The name of the record that the item lives in.
* @param address The address of the record item that the item resides in.
* @param key The key of the item to delete.
*/
deleteItem(recordName: string, address: string, key: TKey): Promise<void>;
deleteItem(
recordName: string,
address: string,
key: TKey
): Promise<CrudResult>;

/**
* Gets a list of the items for the given record and address.
Expand Down Expand Up @@ -84,6 +90,7 @@ export interface GetSubCrudItemResult<T> {

/**
* The markers that are related to the item.
* Null if the parent record item doesn't exist.
*/
markers: string[];
}
Expand All @@ -100,3 +107,15 @@ export interface ListSubCrudStoreSuccess<T> {
*/
totalCount: number;
}

export type CrudResult = CrudSuccess | CrudFailure;

export interface CrudSuccess {
success: true;
}

export interface CrudFailure {
success: false;
errorCode: 'item_not_found' | 'parent_not_found';
errorMessage: string;
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { MemoryCrudRecordsStore } from '../../crud/MemoryCrudRecordsStore';
import { MemorySubCrudRecordsStore } from '../../crud/sub/MemorySubCrudRecordsStore';
import {
PackageVersion,
PackageRecordVersion,
ListedPackageVersion,
PackageVersionRecordsStore,
PackageVersionSubscriptionMetrics,
PackageRecordVersionKey,
} from './PackageVersionRecordsStore';
import { SubscriptionFilter } from '../../MetricsStore';

/**
* A Memory-based implementation of the PackageRecordsStore.
*/
export class MemoryPackageVersionRecordsStore
extends MemoryCrudRecordsStore<PackageRecordVersion>
extends MemorySubCrudRecordsStore<
PackageRecordVersionKey,
PackageRecordVersion
>
implements PackageVersionRecordsStore
{
async getSubscriptionMetrics(
Expand All @@ -30,8 +34,10 @@ export class MemoryPackageVersionRecordsStore
const items = this.getItemRecord(record.name);
totalItems += items.size;

for (let item of items.values()) {
totalPackageVersionBytes += item.sizeInBytes;
for (let versions of items.values()) {
for (let version of versions) {
totalPackageVersionBytes += version.sizeInBytes;
}
}
}

Expand Down
Loading

0 comments on commit e4371c8

Please sign in to comment.