feat: API key sales channel link (#6851)

What
- Add link between API key and sales channels
- Add API route for batch adding sales channels to a publishable API key
- Clean up API key API routes responses
- Move API key test suite from `integration-tests/modules` to `integration-tests/api`
This commit is contained in:
Oli Juhl
2024-03-28 10:15:11 +00:00
committed by GitHub
parent 6ee2ee845c
commit ea8d9d4d42
21 changed files with 568 additions and 260 deletions
@@ -1,18 +1,19 @@
import { revokeApiKeysWorkflow } from "@medusajs/core-flows"
import { RevokeApiKeyDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../types/routing"
import { RevokeApiKeyDTO } from "@medusajs/types"
import { revokeApiKeysWorkflow } from "@medusajs/core-flows"
export const POST = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const id = req.params.id
const { result, errors } = await revokeApiKeysWorkflow(req.scope).run({
const { errors } = await revokeApiKeysWorkflow(req.scope).run({
input: {
selector: { id: req.params.id },
revoke: {
@@ -27,5 +28,17 @@ export const POST = async (
throw errors[0].error
}
res.status(200).json({ apiKey: result[0] })
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "api_key",
variables: {
id: req.params.id,
},
fields: req.remoteQueryConfig.fields,
})
const [apiKey] = await remoteQuery(queryObject)
res.status(200).json({ api_key: apiKey })
}
@@ -1,21 +1,24 @@
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import {
deleteApiKeysWorkflow,
updateApiKeysWorkflow,
} from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import { UpdateApiKeyDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { defaultAdminApiKeyFields } from "../query-config"
import { remoteQueryObjectFromString } from "@medusajs/utils"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve("remoteQuery")
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const variables = { id: req.params.id }
@@ -46,7 +49,19 @@ export const POST = async (
throw errors[0].error
}
res.status(200).json({ apiKey: result[0] })
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "api_key",
variables: {
id: req.params.id,
},
fields: req.remoteQueryConfig.fields,
})
const [apiKey] = await remoteQuery(queryObject)
res.status(200).json({ api_key: apiKey })
}
export const DELETE = async (
@@ -0,0 +1,62 @@
import { addSalesChannelsToApiKeyWorkflow } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
ContainerRegistrationKeys,
MedusaError,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../../../types/routing"
import { AdminPostApiKeysApiKeySalesChannelsBatchReq } from "../../../../validators"
export const POST = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const body = req.validatedBody as AdminPostApiKeysApiKeySalesChannelsBatchReq
const apiKeyModule = req.scope.resolve(ModuleRegistrationName.API_KEY)
const apiKey = await apiKeyModule.retrieve(req.params.id)
if (apiKey.type !== "publishable") {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Sales channels can only be associated with publishable API keys"
)
}
const workflowInput = {
data: [
{
api_key_id: req.params.id,
sales_channel_ids: body.sales_channel_ids,
},
],
}
const { errors } = await addSalesChannelsToApiKeyWorkflow(req.scope).run({
input: workflowInput,
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const query = remoteQueryObjectFromString({
entryPoint: "api_key",
fields: req.remoteQueryConfig.fields,
variables: {
id: req.params.id,
},
})
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const [result] = await remoteQuery(query)
res.status(200).json({ api_key: result })
}
@@ -1,13 +1,14 @@
import * as QueryConfig from "./query-config"
import { transformBody, transformQuery } from "../../../api/middlewares"
import {
AdminGetApiKeysApiKeyParams,
AdminGetApiKeysParams,
AdminPostApiKeysApiKeyReq,
AdminPostApiKeysApiKeySalesChannelsBatchReq,
AdminPostApiKeysReq,
AdminRevokeApiKeysApiKeyReq,
} from "./validators"
import { transformBody, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { authenticate } from "../../../utils/authenticate-middleware"
@@ -40,12 +41,24 @@ export const adminApiKeyRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["POST"],
matcher: "/admin/api-keys",
middlewares: [transformBody(AdminPostApiKeysReq)],
middlewares: [
transformQuery(
AdminGetApiKeysApiKeyParams,
QueryConfig.retrieveTransformQueryConfig
),
transformBody(AdminPostApiKeysReq),
],
},
{
method: ["POST"],
matcher: "/admin/api-keys/:id",
middlewares: [transformBody(AdminPostApiKeysApiKeyReq)],
middlewares: [
transformQuery(
AdminGetApiKeysApiKeyParams,
QueryConfig.retrieveTransformQueryConfig
),
transformBody(AdminPostApiKeysApiKeyReq),
],
},
{
method: ["DELETE"],
@@ -55,6 +68,23 @@ export const adminApiKeyRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["POST"],
matcher: "/admin/api-keys/:id/revoke",
middlewares: [transformBody(AdminRevokeApiKeysApiKeyReq)],
middlewares: [
transformQuery(
AdminGetApiKeysApiKeyParams,
QueryConfig.retrieveTransformQueryConfig
),
transformBody(AdminRevokeApiKeysApiKeyReq),
],
},
{
method: ["POST"],
matcher: "/admin/api-keys/:id/sales-channels/batch/add",
middlewares: [
transformQuery(
AdminGetApiKeysApiKeyParams,
QueryConfig.retrieveTransformQueryConfig
),
transformBody(AdminPostApiKeysApiKeySalesChannelsBatchReq),
],
},
]
@@ -1,5 +1,3 @@
export const defaultAdminApiKeyRelations = []
export const allowedAdminApiKeyRelations = []
export const defaultAdminApiKeyFields = [
"id",
"title",
@@ -11,16 +9,17 @@ export const defaultAdminApiKeyFields = [
"created_by",
"revoked_at",
"revoked_by",
"sales_channels.id",
"sales_channels.name",
]
export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminApiKeyFields,
defaultRelations: defaultAdminApiKeyRelations,
allowedRelations: allowedAdminApiKeyRelations,
defaults: defaultAdminApiKeyFields,
isList: false,
}
export const listTransformQueryConfig = {
...retrieveTransformQueryConfig,
defaultLimit: 20,
isList: true,
}
@@ -1,34 +1,33 @@
import { createApiKeysWorkflow } from "@medusajs/core-flows"
import { CreateApiKeyDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import { CreateApiKeyDTO } from "@medusajs/types"
import { createApiKeysWorkflow } from "@medusajs/core-flows"
import { defaultAdminApiKeyFields } from "./query-config"
import { remoteQueryObjectFromString } from "@medusajs/utils"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve("remoteQuery")
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "api_key",
variables: {
filters: req.filterableFields,
order: req.listConfig.order,
skip: req.listConfig.skip,
take: req.listConfig.take,
...req.remoteQueryConfig.pagination,
},
fields: defaultAdminApiKeyFields,
fields: req.remoteQueryConfig.fields,
})
const { rows: apiKeys, metadata } = await remoteQuery(queryObject)
res.json({
apiKeys,
api_keys: apiKeys,
count: metadata.count,
offset: metadata.skip,
limit: metadata.take,
@@ -55,5 +54,7 @@ export const POST = async (
throw errors[0].error
}
res.status(200).json({ apiKey: result[0] })
// We cannot use remoteQuery here, as we need to show the secret key in the response (and never again)
// And the only time we get to see the secret, is when we create it
res.status(200).json({ api_key: result[0] })
}
@@ -1,4 +1,4 @@
import { OperatorMap } from "@medusajs/types"
import { ApiKeyType } from "@medusajs/utils"
import { Type } from "class-transformer"
import {
IsArray,
@@ -9,8 +9,6 @@ import {
ValidateNested,
} from "class-validator"
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
import { OperatorMapValidator } from "../../../types/validators/operator-map"
import { ApiKeyType } from "@medusajs/utils"
export class AdminGetApiKeysApiKeyParams extends FindParams {}
/**
@@ -80,3 +78,8 @@ export class AdminRevokeApiKeysApiKeyReq {
}
export class AdminDeleteApiKeysApiKeyReq {}
export class AdminPostApiKeysApiKeySalesChannelsBatchReq {
@IsArray()
sales_channel_ids: string[]
}
@@ -4,10 +4,9 @@ import {
} from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
import { AdminPostStockLocationsLocationReq } from "../validators"
import { deleteStockLocationsWorkflow, updateStockLocationsWorkflow } from "@medusajs/core-flows"
import { MedusaError } from "@medusajs/utils"
import { deleteStockLocationsWorkflow } from "@medusajs/core-flows"
import { updateStockLocationsWorkflow } from "@medusajs/core-flows"
import { AdminPostStockLocationsLocationReq } from "../validators"
export const POST = async (
req: MedusaRequest<AdminPostStockLocationsLocationReq>,
@@ -1,3 +1 @@
export * as publishableApiKey from "./publishable-api-key-service"
export * as shippingProfile from "./shipping-profile-service"
@@ -1,28 +0,0 @@
import { ModuleJoinerConfig } from "@medusajs/types"
export default {
serviceName: "publishableApiKeyService",
primaryKeys: ["id"],
linkableKeys: { publishable_key_id: "PublishableApiKey" },
schema: `
scalar Date
scalar JSON
type PublishableApiKey {
id: ID!
sales_channel_id: String!
publishable_key_id: String!
created_at: Date!
updated_at: Date!
deleted_at: Date
}
`,
alias: [
{
name: ["publishable_api_key", "publishable_api_keys"],
args: {
entity: "PublishableApiKey",
},
},
],
} as ModuleJoinerConfig