Files
cfw-autumn/shared/utils/featureUtils/apiFeatureToDbFeature.ts
2026-04-14 17:46:08 +01:00

272 lines
7.3 KiB
TypeScript

import {
ApiFeatureType,
type ApiFeatureV0,
} from "@api/features/prevVersions/apiFeatureV0.js";
import type { UpdateFeatureParams } from "@api/features/updateFeatureParams.js";
import {
FeatureType,
FeatureUsageType,
} from "@models/featureModels/featureEnums.js";
import type { Feature } from "@models/featureModels/featureModels.js";
import { AppEnv } from "@models/genModels/genEnums.js";
import type { ApiFeatureV1 } from "../../api/features/apiFeatureV1.js";
import type {
CreateFeatureV1Params,
UpdateFeatureV1Params,
} from "../../api/models.js";
import {
AffectedResource,
ApiVersionClass,
applyResponseVersionChanges,
LATEST_VERSION,
} from "../../api/versionUtils/versionUtils.js";
import type { CreditSchemaItem } from "../../models/featureModels/featureConfig/creditConfig.js";
import type { SharedContext } from "../../types/sharedContext.js";
import { notNullish, nullish } from "../utils.js";
export const apiFeatureToDbFeature = ({
apiFeature,
originalFeature,
}: {
apiFeature: ApiFeatureV0 | UpdateFeatureParams;
originalFeature?: Feature;
}) => {
// Replace body...
let featureType = apiFeature.type as unknown as FeatureType;
let usageType: FeatureUsageType | undefined;
if (
apiFeature.type === ApiFeatureType.SingleUsage ||
apiFeature.type === ApiFeatureType.ContinuousUse
) {
featureType = FeatureType.Metered;
usageType = apiFeature.type as unknown as FeatureUsageType;
}
const newConfig =
featureType === FeatureType.Boolean
? undefined
: originalFeature?.config || {};
if (usageType) {
newConfig.usage_type = usageType;
}
if (apiFeature.credit_schema) {
newConfig.schema = apiFeature.credit_schema.map(
(credit: { metered_feature_id: string; credit_cost: number }) => ({
metered_feature_id: credit.metered_feature_id,
credit_amount: credit.credit_cost,
}),
);
}
return {
internal_id: originalFeature?.internal_id ?? "",
org_id: originalFeature?.org_id ?? "",
created_at: originalFeature?.created_at ?? Date.now(),
env: originalFeature?.env ?? AppEnv.Sandbox,
id: apiFeature.id ?? originalFeature?.id ?? "",
name: apiFeature.name ?? originalFeature?.name ?? "",
type: featureType,
config: newConfig,
archived: apiFeature.archived ?? originalFeature?.archived ?? false,
event_names: [],
} satisfies Feature;
};
export const featureV1ToDbFeatureConfig = ({
apiFeature,
originalFeature,
}: {
apiFeature: UpdateFeatureV1Params;
originalFeature: Feature;
}) => {
const type = apiFeature.type || originalFeature.type;
if (nullish(apiFeature.consumable) && nullish(apiFeature.credit_schema))
return;
if (type === FeatureType.Boolean) return;
if (type === FeatureType.Metered) {
const newUsageType = notNullish(apiFeature.consumable)
? apiFeature.consumable
? FeatureUsageType.Single
: FeatureUsageType.Continuous
: originalFeature.config?.usage_type;
return {
usage_type: newUsageType,
};
}
if (type === FeatureType.CreditSystem) {
const newSchema = notNullish(apiFeature.credit_schema)
? apiFeature.credit_schema.map(
(credit: { metered_feature_id: string; credit_cost: number }) => ({
metered_feature_id: credit.metered_feature_id,
credit_amount: credit.credit_cost,
}),
)
: originalFeature.config?.schema;
return {
schema: newSchema,
usage_type: FeatureUsageType.Single,
};
}
return undefined;
};
export const featureV1ToDbFeature = ({
apiFeature,
originalFeature,
}: {
apiFeature: ApiFeatureV1 | CreateFeatureV1Params;
originalFeature?: Feature;
}) => {
// Replace body...
const featureType = apiFeature.type;
const eventNames = apiFeature.event_names;
const newConfig =
featureType === FeatureType.Boolean
? undefined
: originalFeature?.config || {};
if (apiFeature.type === FeatureType.Metered) {
newConfig.usage_type = apiFeature.consumable
? FeatureUsageType.Single
: FeatureUsageType.Continuous;
}
if (apiFeature.credit_schema) {
newConfig.usage_type = FeatureUsageType.Single;
newConfig.schema = apiFeature.credit_schema.map(
(credit: { metered_feature_id: string; credit_cost: number }) => ({
metered_feature_id: credit.metered_feature_id,
credit_amount: credit.credit_cost,
}),
);
}
return {
internal_id: originalFeature?.internal_id ?? "",
org_id: originalFeature?.org_id ?? "",
created_at: originalFeature?.created_at ?? Date.now(),
env: originalFeature?.env ?? AppEnv.Sandbox,
id: apiFeature.id ?? originalFeature?.id ?? "",
name: apiFeature.name ?? originalFeature?.name ?? "",
type: featureType,
config: newConfig,
archived:
"archived" in apiFeature
? apiFeature.archived
: (originalFeature?.archived ?? false),
event_names: eventNames ?? [],
} satisfies Feature;
};
/**
* Converts a database feature to the V1 API format (latest format).
*
* Version handling:
* - This function always returns ApiFeatureV1 (V2.0+ format)
* - Automatic version transformation to older formats (V0) happens via V1.2_FeatureChange
* - The transformation is applied by the middleware when handlers use resource: AffectedResource.Feature
* - For API version V1_Beta and older, responses are automatically converted to ApiFeatureV0 format
*/
export const dbToApiFeatureV1 = ({
ctx,
dbFeature,
targetVersion,
}: {
ctx: SharedContext;
dbFeature: Feature;
targetVersion?: ApiVersionClass;
}) => {
const result = {
id: dbFeature.id,
name: dbFeature.name,
type: dbFeature.type,
consumable:
dbFeature.type === FeatureType.CreditSystem ||
dbFeature.config?.usage_type === FeatureUsageType.Single,
credit_schema: Array.isArray(dbFeature.config?.schema)
? dbFeature.config.schema.map((schema: CreditSchemaItem) => ({
metered_feature_id: schema.metered_feature_id,
credit_cost: schema.credit_amount,
}))
: undefined,
event_names: Array.isArray(dbFeature.event_names)
? dbFeature.event_names
: [],
archived: dbFeature.archived,
display: dbFeature.display
? {
singular: dbFeature.display.singular,
plural: dbFeature.display.plural,
}
: undefined,
} satisfies ApiFeatureV1;
return applyResponseVersionChanges({
input: result,
targetVersion: targetVersion ?? new ApiVersionClass(LATEST_VERSION),
resource: AffectedResource.Feature,
ctx,
});
};
// export const fromApiFeature = ({
// apiFeature,
// orgId,
// env,
// }: {
// apiFeature: ApiFeatureV0;
// orgId: string;
// env: AppEnv;
// }) => {
// const isMetered =
// apiFeature.type === ApiFeatureType.SingleUsage ||
// apiFeature.type === ApiFeatureType.ContinuousUse;
// const featureType: FeatureType = isMetered
// ? FeatureType.Metered
// : (apiFeature.type as unknown as FeatureType);
// if (isMetered) {
// return constructMeteredFeature({
// featureId: apiFeature.id,
// name: apiFeature.name || "",
// usageType: apiFeature.type as unknown as FeatureUsageType,
// orgId,
// env,
// });
// }
// if (featureType === FeatureType.CreditSystem) {
// if (!apiFeature.credit_schema || apiFeature.credit_schema.length === 0) {
// throw new Error("Credit system schema is required");
// }
// return constructCreditSystem({
// featureId: apiFeature.id,
// name: apiFeature.name || "",
// orgId,
// env,
// schema: apiFeature.credit_schema!,
// });
// }
// return constructBooleanFeature({
// featureId: apiFeature.id,
// name: apiFeature.name || "",
// orgId,
// env,
// });
// };