Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
krzysztofzuraw committed Jan 13, 2025
1 parent 89b5289 commit dbf5a10
Show file tree
Hide file tree
Showing 15 changed files with 409 additions and 64 deletions.
2 changes: 1 addition & 1 deletion apps/segment/src/lib/dynamodb/dynamodb-apl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { APL, AplConfiguredResult, AplReadyResult, AuthData } from "@saleor/app-

import { BaseError } from "@/errors";

import { SegmentAPLRepository } from "./segment-apl-repository";
import { SegmentConfigTable, SegmentConfigTableEntityFactory } from "./segment-config-table";
import { SegmentAPLRepository } from "./segment-repository";

export class DynamoDBApl implements APL {
private segmentAplRepository: SegmentAPLRepository;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class SegmentAPLRepository {
this.deps.segmentAPLEntity
.build(GetItemCommand)
.key({
PK: SegmentConfigTable.getPrimaryKey({
PK: SegmentConfigTable.getAPLPrimaryKey({
appManifestId: args.appManifestId,
}),
SK: SegmentConfigTable.getAPLSortKey({
Expand Down Expand Up @@ -60,7 +60,7 @@ export class SegmentAPLRepository {
this.deps.segmentAPLEntity
.build(PutItemCommand)
.item({
PK: SegmentConfigTable.getPrimaryKey({
PK: SegmentConfigTable.getAPLPrimaryKey({
appManifestId: args.appManifestId,
}),
SK: SegmentConfigTable.getAPLSortKey({
Expand Down Expand Up @@ -93,7 +93,7 @@ export class SegmentAPLRepository {
this.deps.segmentAPLEntity
.build(DeleteItemCommand)
.key({
PK: SegmentConfigTable.getPrimaryKey({
PK: SegmentConfigTable.getAPLPrimaryKey({
appManifestId: args.appManifestId,
}),
SK: SegmentConfigTable.getAPLSortKey({
Expand Down Expand Up @@ -121,7 +121,7 @@ export class SegmentAPLRepository {
this.deps.segmentAPLEntity.table
.build(QueryCommand)
.query({
partition: SegmentConfigTable.getPrimaryKey({ appManifestId: args.appManifestId }),
partition: SegmentConfigTable.getAPLPrimaryKey({ appManifestId: args.appManifestId }),
})
.entities(this.deps.segmentAPLEntity)
.send(),
Expand Down
93 changes: 93 additions & 0 deletions apps/segment/src/lib/dynamodb/segment-config-repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { GetItemCommand, PutItemCommand } from "dynamodb-toolbox";
import { err, ok, ResultAsync } from "neverthrow";

import { BaseError } from "@/errors";
import { createLogger } from "@/logger";

import { SegmentConfigEntity, SegmentConfigTable } from "./segment-config-table";

export class SegmentConfigRepository {
private logger = createLogger("SegmentConfigRepository");

static ReadEntityError = BaseError.subclass("ReadEntityError");
static WriteEntityError = BaseError.subclass("WriteEntityError");

constructor(
private deps: {
segmentConfigEntity: SegmentConfigEntity;
},
) {}

async getConfig(args: { appId: string; saleorApiUrl: string; configKey: string }) {
const getEntryResult = await ResultAsync.fromPromise(
this.deps.segmentConfigEntity
.build(GetItemCommand)
.key({
PK: SegmentConfigTable.getConfigPrimaryKey({
saleorApiUrl: args.saleorApiUrl,
appId: args.appId,
}),
SK: SegmentConfigTable.getConfigSortKey({
configKey: args.configKey,
}),
})
.send(),
(error) =>
new SegmentConfigRepository.ReadEntityError("Failed to read APL entity", { cause: error }),
);

if (getEntryResult.isErr()) {
this.logger.error("Error while reading config entity from DynamoDB", {
error: getEntryResult.error,
});

return err(getEntryResult.error);
}

if (!getEntryResult.value.Item) {
this.logger.warn("Config entry not found", { args });

return err(new SegmentConfigRepository.ReadEntityError("Config entry not found"));
}

return ok(getEntryResult.value.Item);
}

async setConfig(args: {
appId: string;
saleorApiUrl: string;
configKey: string;
configValue: string;
}) {
const setEntryResult = await ResultAsync.fromPromise(
this.deps.segmentConfigEntity
.build(PutItemCommand)
.item({
PK: SegmentConfigTable.getConfigPrimaryKey({
saleorApiUrl: args.saleorApiUrl,
appId: args.appId,
}),
SK: SegmentConfigTable.getConfigSortKey({
configKey: args.configKey,
}),
// TODO: encrypt this value
value: args.configValue,
})
.send(),
(error) =>
new SegmentConfigRepository.WriteEntityError("Failed to write config entity", {
cause: error,
}),
);

if (setEntryResult.isErr()) {
this.logger.error("Error while putting config into DynamoDB", {
error: setEntryResult.error,
});

return err(setEntryResult.error);
}

return ok(undefined);
}
}
37 changes: 35 additions & 2 deletions apps/segment/src/lib/dynamodb/segment-config-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,20 @@ export class SegmentConfigTable extends Table<
});
}

static getPrimaryKey({ appManifestId }: { appManifestId: string }) {
static getAPLPrimaryKey({ appManifestId }: { appManifestId: string }) {
return `${appManifestId}` as const;
}

static getAPLSortKey({ saleorApiUrl }: { saleorApiUrl: string }) {
return `${saleorApiUrl}` as const;
return `APL#${saleorApiUrl}` as const;
}

static getConfigPrimaryKey({ saleorApiUrl, appId }: { saleorApiUrl: string; appId: string }) {
return `${saleorApiUrl}#${appId}` as const;
}

static getConfigSortKey({ configKey }: { configKey: string }) {
return `APP_CONFIG#${configKey}` as const;
}
}

Expand All @@ -61,6 +69,11 @@ const SegmentConfigTableSchema = {
appId: string(),
jwks: string().optional(),
}),
config: schema({
PK: string().key(),
SK: string().key(),
value: string(),
}),
};

export const client = createDynamoDBClient();
Expand All @@ -85,6 +98,26 @@ export const SegmentConfigTableEntityFactory = {
},
});
},
createConfigEntity: (table: SegmentConfigTable) => {
return new Entity({
table,
name: "Config",
schema: SegmentConfigTableSchema.config,
timestamps: {
created: {
name: "createdAt",
savedAs: "createdAt",
},
modified: {
name: "modifiedAt",
savedAs: "modifiedAt",
},
},
});
},
};

export type SegmentAPLEntity = ReturnType<typeof SegmentConfigTableEntityFactory.createAPLEntity>;
export type SegmentConfigEntity = ReturnType<
typeof SegmentConfigTableEntityFactory.createConfigEntity
>;
25 changes: 13 additions & 12 deletions apps/segment/src/modules/configuration/configuration.router.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { TRPCError } from "@trpc/server";
import { z } from "zod";

import { env } from "@/env";
import { documentClient } from "@/lib/dynamodb/segment-config-table";
import { createLogger } from "@/logger";

import { protectedClientProcedure } from "../trpc/protected-client-procedure";
import { router } from "../trpc/trpc-server";
import { WebhooksActivityClient } from "../webhooks/webhook-activity/webhook-activity-client";
import { WebhookActivityService } from "../webhooks/webhook-activity/webhook-activity-service";
import { AppConfigMetadataManager } from "./app-config-metadata-manager";
import { DynamoDBConfigManager } from "./dynamodb-config-manager";

const logger = createLogger("configurationRouter");

const manager = new DynamoDBConfigManager({
documentClient: documentClient,
tableName: env.DYNAMODB_CONFIG_TABLE_NAME ?? "",
});

export const configurationRouter = router({
getWebhookConfig: protectedClientProcedure.query(async ({ ctx }) => {
const webhookActivityClient = new WebhooksActivityClient(ctx.apiClient);
Expand All @@ -30,30 +37,24 @@ export const configurationRouter = router({
return { areWebhooksActive: isActiveResult.value.some(Boolean) };
}),
getConfig: protectedClientProcedure.query(async ({ ctx }) => {
const manager = AppConfigMetadataManager.createFromAuthData({
appId: ctx.appId,
const config = await manager.get({
saleorApiUrl: ctx.saleorApiUrl,
token: ctx.appToken,
appId: ctx.appId,
});

const config = await manager.get();

logger.debug("Successfully fetched config");

return config.getConfig();
}),
setConfig: protectedClientProcedure.input(z.string().min(1)).mutation(async ({ input, ctx }) => {
const manager = AppConfigMetadataManager.createFromAuthData({
appId: ctx.appId,
const config = await manager.get({
saleorApiUrl: ctx.saleorApiUrl,
token: ctx.appToken,
appId: ctx.appId,
});

const config = await manager.get();

config.setSegmentWriteKey(input);

await manager.set(config);
await manager.set({ config, saleorApiUrl: ctx.saleorApiUrl, appId: ctx.appId });

logger.debug("Successfully set config");

Expand Down
68 changes: 68 additions & 0 deletions apps/segment/src/modules/configuration/dynamodb-config-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";

import { BaseError } from "@/errors";
import { SegmentConfigRepository } from "@/lib/dynamodb/segment-config-repository";
import {
SegmentConfigTable,
SegmentConfigTableEntityFactory,
} from "@/lib/dynamodb/segment-config-table";

import { AppConfig } from "./app-config";

export interface UpdatedAppConfigMetadataManager {
// TODO: add get and create or get - to be discussed
get(args: { saleorApiUrl: string; appId: string }): Promise<AppConfig>;
set(args: { config: AppConfig; saleorApiUrl: string; appId: string }): Promise<void>;
}

export class DynamoDBConfigManager implements UpdatedAppConfigMetadataManager {
private segmentConfigRepository: SegmentConfigRepository;

public readonly configKey = "app-config-v1";

constructor({
documentClient,
tableName,
}: {
documentClient: DynamoDBDocumentClient;
tableName: string;
}) {
const table = SegmentConfigTable.create({
tableName,
documentClient,
});

const segmentConfigEntity = SegmentConfigTableEntityFactory.createConfigEntity(table);

this.segmentConfigRepository = new SegmentConfigRepository({ segmentConfigEntity });
}

async get(args: { saleorApiUrl: string; appId: string }): Promise<AppConfig> {
const possibleResult = await this.segmentConfigRepository.getConfig({
saleorApiUrl: args.saleorApiUrl,
appId: args.appId,
configKey: this.configKey,
});

if (possibleResult.isErr()) {
// TODO: what do there?
return new AppConfig();
}

return AppConfig.parse(possibleResult.value.value);
}
async set(args: { config: AppConfig; saleorApiUrl: string; appId: string }): Promise<void> {
const possibleResult = await this.segmentConfigRepository.setConfig({
appId: args.appId,
saleorApiUrl: args.saleorApiUrl,
configKey: this.configKey,
configValue: args.config.serialize(),
});

if (possibleResult.isErr()) {
throw new BaseError("Failed to set config entry");
}

return undefined;
}
}
Loading

0 comments on commit dbf5a10

Please sign in to comment.