Skip to content

Commit

Permalink
chore(api-key): Refactor API to follow new module interface
Browse files Browse the repository at this point in the history
  • Loading branch information
sradevski committed Feb 27, 2024
1 parent debf5e0 commit c8a5b9b
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 104 deletions.
167 changes: 99 additions & 68 deletions packages/api-key/src/services/api-key-module-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import {
} from "@medusajs/types"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import { ApiKey } from "@models"
import { CreateApiKeyDTO, TokenDTO } from "@types"
import {
CreateApiKeyDTO,
RevokeApiKeyInput,
TokenDTO,
UpdateApiKeyInput,
} from "@types"
import {
ApiKeyType,
InjectManager,
Expand Down Expand Up @@ -133,61 +138,109 @@ export default class ApiKeyModuleService<TEntity extends ApiKey = ApiKey>
return [createdApiKeys, generatedTokens]
}

async update(
selector: FilterableApiKeyProps,
data: Omit<ApiKeyTypes.UpdateApiKeyDTO, "id">,
async upsert(
data: ApiKeyTypes.UpsertApiKeyDTO[],
sharedContext?: Context
): Promise<ApiKeyTypes.ApiKeyDTO[]>
async upsert(
data: ApiKeyTypes.UpsertApiKeyDTO,
sharedContext?: Context
): Promise<ApiKeyTypes.ApiKeyDTO>
@InjectTransactionManager("baseRepository_")
async upsert(
data: ApiKeyTypes.UpsertApiKeyDTO | ApiKeyTypes.UpsertApiKeyDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<ApiKeyTypes.ApiKeyDTO | ApiKeyTypes.ApiKeyDTO[]> {
const input = Array.isArray(data) ? data : [data]
const forUpdate = input.filter(
(apiKey): apiKey is UpdateApiKeyInput => !!apiKey.id
)
const forCreate = input.filter(
(apiKey): apiKey is ApiKeyTypes.CreateApiKeyDTO => !apiKey.id
)

const operations: Promise<ApiKeyTypes.ApiKeyDTO[]>[] = []

if (forCreate.length) {
const op = async () => {
const [createdApiKeys, generatedTokens] = await this.create_(
forCreate,
sharedContext
)
const serializedResponse = await this.baseRepository_.serialize<
ApiKeyTypes.ApiKeyDTO[]
>(createdApiKeys, {
populate: true,
})

return serializedResponse.map(
(key) =>
({
...key,
token:
generatedTokens.find((t) => t.hashedToken === key.token)
?.rawToken ?? key.token,
salt: undefined,
} as ApiKeyTypes.ApiKeyDTO)
)
}

operations.push(op())
}

if (forUpdate.length) {
const op = async () => {
const updateResp = await this.update_(forUpdate, sharedContext)
return await this.baseRepository_.serialize<ApiKeyTypes.ApiKeyDTO[]>(
updateResp
)
}

operations.push(op())
}

const result = (await promiseAll(operations)).flat()
return Array.isArray(data) ? result : result[0]
}

async update(
id: string,
data: Omit<ApiKeyTypes.UpdateApiKeyDTO, "id">,
data: ApiKeyTypes.UpdateApiKeyDTO,
sharedContext?: Context
): Promise<ApiKeyTypes.ApiKeyDTO>
async update(
data: ApiKeyTypes.UpdateApiKeyDTO[]
selector: FilterableApiKeyProps,
data: ApiKeyTypes.UpdateApiKeyDTO,
sharedContext?: Context
): Promise<ApiKeyTypes.ApiKeyDTO[]>
@InjectManager("baseRepository_")
async update(
idOrSelectorOrData:
| string
| FilterableApiKeyProps
| ApiKeyTypes.UpdateApiKeyDTO[],
data?: Omit<ApiKeyTypes.UpdateApiKeyDTO, "id">,
idOrSelector: string | FilterableApiKeyProps,
data: ApiKeyTypes.UpdateApiKeyDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<ApiKeyTypes.ApiKeyDTO[] | ApiKeyTypes.ApiKeyDTO> {
const updatedApiKeys = await this.update_(
idOrSelectorOrData,
let normalizedInput = await this.normalizeUpdateInput_<UpdateApiKeyInput>(
idOrSelector,
data,
sharedContext
)

const updatedApiKeys = await this.update_(normalizedInput, sharedContext)

const serializedResponse = await this.baseRepository_.serialize<
ApiKeyTypes.ApiKeyDTO[]
>(updatedApiKeys.map(omitToken), {
populate: true,
})

return isString(idOrSelectorOrData)
? serializedResponse[0]
: serializedResponse
return isString(idOrSelector) ? serializedResponse[0] : serializedResponse
}

@InjectTransactionManager("baseRepository_")
protected async update_(
idOrSelectorOrData:
| string
| FilterableApiKeyProps
| ApiKeyTypes.UpdateApiKeyDTO[],
data?: Omit<ApiKeyTypes.UpdateApiKeyDTO, "id">,
normalizedInput: UpdateApiKeyInput[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
const normalizedInput =
await this.normalizeUpdateInput_<ApiKeyTypes.UpdateApiKeyDTO>(
idOrSelectorOrData,
data,
sharedContext
)

const updateRequest = normalizedInput.map((k) => ({
id: k.id,
title: k.title,
Expand Down Expand Up @@ -259,61 +312,43 @@ export default class ApiKeyModuleService<TEntity extends ApiKey = ApiKey>
]
}

async revoke(
selector: FilterableApiKeyProps,
data: Omit<ApiKeyTypes.RevokeApiKeyDTO, "id">,
sharedContext?: Context
): Promise<ApiKeyTypes.ApiKeyDTO[]>
async revoke(
id: string,
data: Omit<ApiKeyTypes.RevokeApiKeyDTO, "id">,
data: ApiKeyTypes.RevokeApiKeyDTO,
sharedContext?: Context
): Promise<ApiKeyTypes.ApiKeyDTO>
async revoke(
data: ApiKeyTypes.RevokeApiKeyDTO[]
selector: FilterableApiKeyProps,
data: ApiKeyTypes.RevokeApiKeyDTO,
sharedContext?: Context
): Promise<ApiKeyTypes.ApiKeyDTO[]>
@InjectManager("baseRepository_")
async revoke(
idOrSelectorOrData:
| string
| FilterableApiKeyProps
| ApiKeyTypes.RevokeApiKeyDTO[],
data?: Omit<ApiKeyTypes.RevokeApiKeyDTO, "id">,
idOrSelector: string | FilterableApiKeyProps,
data: ApiKeyTypes.RevokeApiKeyDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<ApiKeyTypes.ApiKeyDTO[] | ApiKeyTypes.ApiKeyDTO> {
const revokedApiKeys = await this.revoke_(
idOrSelectorOrData,
const normalizedInput = await this.normalizeUpdateInput_<RevokeApiKeyInput>(
idOrSelector,
data,
sharedContext
)
const revokedApiKeys = await this.revoke_(normalizedInput, sharedContext)

const serializedResponse = await this.baseRepository_.serialize<
ApiKeyTypes.ApiKeyDTO[]
>(revokedApiKeys.map(omitToken), {
populate: true,
})

return isString(idOrSelectorOrData)
? serializedResponse[0]
: serializedResponse
return isString(idOrSelector) ? serializedResponse[0] : serializedResponse
}

@InjectTransactionManager("baseRepository_")
async revoke_(
idOrSelectorOrData:
| string
| FilterableApiKeyProps
| ApiKeyTypes.RevokeApiKeyDTO[],
data?: Omit<ApiKeyTypes.RevokeApiKeyDTO, "id">,
normalizedInput: RevokeApiKeyInput[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
const normalizedInput =
await this.normalizeUpdateInput_<ApiKeyTypes.RevokeApiKeyDTO>(
idOrSelectorOrData,
data,
sharedContext
)

await this.validateRevokeApiKeys_(normalizedInput)

const updateRequest = normalizedInput.map((k) => {
Expand Down Expand Up @@ -424,22 +459,18 @@ export default class ApiKeyModuleService<TEntity extends ApiKey = ApiKey>
}

protected async normalizeUpdateInput_<T>(
idOrSelectorOrData: string | FilterableApiKeyProps | T[],
data?: Omit<T, "id">,
idOrSelector: string | FilterableApiKeyProps,
data: Omit<T, "id">,
sharedContext: Context = {}
): Promise<T[]> {
let normalizedInput: T[] = []
if (isString(idOrSelectorOrData)) {
normalizedInput = [{ id: idOrSelectorOrData, ...data } as T]
}

if (Array.isArray(idOrSelectorOrData)) {
normalizedInput = idOrSelectorOrData
if (isString(idOrSelector)) {
normalizedInput = [{ id: idOrSelector, ...data } as T]
}

if (isObject(idOrSelectorOrData)) {
if (isObject(idOrSelector)) {
const apiKeys = await this.apiKeyService_.list(
idOrSelectorOrData,
idOrSelector,
{},
sharedContext
)
Expand All @@ -457,7 +488,7 @@ export default class ApiKeyModuleService<TEntity extends ApiKey = ApiKey>
}

protected async validateRevokeApiKeys_(
data: ApiKeyTypes.RevokeApiKeyDTO[],
data: RevokeApiKeyInput[],
sharedContext: Context = {}
): Promise<void> {
if (!data.length) {
Expand Down
5 changes: 4 additions & 1 deletion packages/api-key/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiKeyType } from "@medusajs/types"
import { ApiKeyType, RevokeApiKeyDTO, UpdateApiKeyDTO } from "@medusajs/types"
import { IEventBusModuleService, Logger } from "@medusajs/types"

export type InitializeModuleInjectableDependencies = {
Expand All @@ -21,3 +21,6 @@ export type TokenDTO = {
salt: string
redacted: string
}

export type UpdateApiKeyInput = UpdateApiKeyDTO & { id: string }
export type RevokeApiKeyInput = RevokeApiKeyDTO & { id: string }
3 changes: 1 addition & 2 deletions packages/core-flows/src/api-key/steps/revoke-api-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import {
IApiKeyModuleService,
RevokeApiKeyDTO,
} from "@medusajs/types"
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"

type RevokeApiKeysStepInput = {
selector: FilterableApiKeyProps
revoke: Omit<RevokeApiKeyDTO, "id">
revoke: RevokeApiKeyDTO
}

export const revokeApiKeysStepId = "revoke-api-keys"
Expand Down
4 changes: 2 additions & 2 deletions packages/core-flows/src/api-key/steps/update-api-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { StepResponse, createStep } from "@medusajs/workflows-sdk"

type UpdateApiKeysStepInput = {
selector: FilterableApiKeyProps
update: Omit<UpdateApiKeyDTO, "id">
update: UpdateApiKeyDTO
}

export const updateApiKeysStepId = "update-api-keys"
Expand Down Expand Up @@ -41,7 +41,7 @@ export const updateApiKeysStep = createStep(
ModuleRegistrationName.API_KEY
)

await service.update(
await service.upsert(
prevData.map((r) => ({
id: r.id,
title: r.title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { revokeApiKeysStep } from "../steps"

type RevokeApiKeysStepInput = {
selector: FilterableApiKeyProps
revoke: Omit<RevokeApiKeyDTO, "id">
revoke: RevokeApiKeyDTO
}

type WorkflowInput = RevokeApiKeysStepInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { updateApiKeysStep } from "../steps"

type UpdateApiKeysStepInput = {
selector: FilterableApiKeyProps
update: Omit<UpdateApiKeyDTO, "id">
update: UpdateApiKeyDTO
}

type WorkflowInput = UpdateApiKeysStepInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const POST = async (
input: {
selector: { id: req.params.id },
revoke: {
...(req.validatedBody as Omit<RevokeApiKeyDTO, "id" | "revoked_by">),
...(req.validatedBody as Omit<RevokeApiKeyDTO, "revoked_by">),
revoked_by: req.auth.actor_id,
} as RevokeApiKeyDTO,
},
Expand Down
2 changes: 1 addition & 1 deletion packages/medusa/src/api-v2/admin/api-keys/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const POST = async (
const { result, errors } = await updateApiKeysWorkflow(req.scope).run({
input: {
selector: { id: req.params.id },
update: req.validatedBody,
update: req.validatedBody as UpdateApiKeyDTO,
},
throwOnError: false,
})
Expand Down
9 changes: 7 additions & 2 deletions packages/types/src/api-key/mutations/api-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ export interface CreateApiKeyDTO {
// We could add revoked_at as a parameter (or expires_at that gets mapped to revoked_at internally) in order to support expiring tokens
}

export interface UpsertApiKeyDTO {
id?: string
title?: string
type?: ApiKeyType
created_by?: string
}

export interface UpdateApiKeyDTO {
id: string
title?: string
}

export interface RevokeApiKeyDTO {
id: string
revoked_by: string
revoke_in?: number // Seconds after which the token should be considered revoked
}
Loading

0 comments on commit c8a5b9b

Please sign in to comment.