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 11:15:11 +01:00
committed by GitHub
parent 6ee2ee845c
commit ea8d9d4d42
21 changed files with 568 additions and 260 deletions

View File

@@ -0,0 +1,8 @@
---
"@medusajs/medusa": patch
"@medusajs/api-key": patch
"@medusajs/core-flows": patch
"@medusajs/link-modules": patch
---
feat: API key sales channel link

View File

@@ -0,0 +1,273 @@
import { ApiKeyType } from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import { createAdminUser } from "../../../helpers/create-admin-user"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, getContainer, api }) => {
describe("API Keys - Admin", () => {
let container
beforeAll(async () => {
container = getContainer()
})
beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, container)
})
it("should correctly implement the entire lifecycle of an api key", async () => {
const created = await api.post(
`/admin/api-keys`,
{
title: "Test Secret Key",
type: ApiKeyType.SECRET,
},
adminHeaders
)
expect(created.status).toEqual(200)
expect(created.data.api_key).toEqual(
expect.objectContaining({
id: created.data.api_key.id,
title: "Test Secret Key",
created_by: "admin_user",
})
)
// On create we get the token in raw form so we can store it.
expect(created.data.api_key.token).toContain("sk_")
const updated = await api.post(
`/admin/api-keys/${created.data.api_key.id}`,
{
title: "Updated Secret Key",
},
adminHeaders
)
expect(updated.status).toEqual(200)
expect(updated.data.api_key).toEqual(
expect.objectContaining({
id: created.data.api_key.id,
title: "Updated Secret Key",
})
)
const revoked = await api.post(
`/admin/api-keys/${created.data.api_key.id}/revoke`,
{},
adminHeaders
)
expect(revoked.status).toEqual(200)
expect(revoked.data.api_key).toEqual(
expect.objectContaining({
id: created.data.api_key.id,
revoked_by: "admin_user",
})
)
expect(revoked.data.api_key.revoked_at).toBeTruthy()
const deleted = await api.delete(
`/admin/api-keys/${created.data.api_key.id}`,
adminHeaders
)
const listedApiKeys = await api.get(`/admin/api-keys`, adminHeaders)
expect(deleted.status).toEqual(200)
expect(listedApiKeys.data.api_keys).toHaveLength(0)
})
it("can use a secret api key for authentication", async () => {
const created = await api.post(
`/admin/api-keys`,
{
title: "Test Secret Key",
type: ApiKeyType.SECRET,
},
adminHeaders
)
const createdRegion = await api.post(
`/admin/regions`,
{
name: "Test Region",
currency_code: "usd",
countries: ["us", "ca"],
},
{
auth: {
username: created.data.api_key.token,
},
}
)
expect(createdRegion.status).toEqual(200)
expect(createdRegion.data.region.name).toEqual("Test Region")
})
it("falls back to other mode of authentication when an api key is not valid", async () => {
const created = await api.post(
`/admin/api-keys`,
{
title: "Test Secret Key",
type: ApiKeyType.SECRET,
},
adminHeaders
)
await api.post(
`/admin/api-keys/${created.data.api_key.id}/revoke`,
{},
adminHeaders
)
const err = await api
.post(
`/admin/regions`,
{
name: "Test Region",
currency_code: "usd",
countries: ["us", "ca"],
},
{
auth: {
username: created.data.api_key.token,
},
}
)
.catch((e) => e.message)
const createdRegion = await api.post(
`/admin/regions`,
{
name: "Test Region",
currency_code: "usd",
countries: ["us", "ca"],
},
{
auth: {
username: created.data.api_key.token,
},
...adminHeaders,
}
)
expect(err).toEqual("Request failed with status code 401")
expect(createdRegion.status).toEqual(200)
expect(createdRegion.data.region.name).toEqual("Test Region")
})
it("should associate sales channels with a publishable API key", async () => {
const salesChannelRes = await api.post(
`/admin/sales-channels`,
{
name: "Test Sales Channel",
},
adminHeaders
)
const { sales_channel } = salesChannelRes.data
const apiKeyRes = await api.post(
`/admin/api-keys`,
{
title: "Test publishable KEY",
type: ApiKeyType.PUBLISHABLE,
},
adminHeaders
)
const { api_key } = apiKeyRes.data
const keyWithChannelsRes = await api.post(
`/admin/api-keys/${api_key.id}/sales-channels/batch/add`,
{
sales_channel_ids: [sales_channel.id],
},
adminHeaders
)
const { api_key: keyWithChannels } = keyWithChannelsRes.data
expect(keyWithChannelsRes.status).toEqual(200)
expect(keyWithChannels.title).toEqual("Test publishable KEY")
expect(keyWithChannels.sales_channels).toEqual([
expect.objectContaining({
id: sales_channel.id,
name: "Test Sales Channel",
}),
])
})
it("should throw if API key is not a publishable key", async () => {
const salesChannelRes = await api.post(
`/admin/sales-channels`,
{
name: "Test Sales Channel",
},
adminHeaders
)
const { sales_channel } = salesChannelRes.data
const apiKeyRes = await api.post(
`/admin/api-keys`,
{
title: "Test secret KEY",
type: ApiKeyType.SECRET,
},
adminHeaders
)
const errorRes = await api
.post(
`/admin/api-keys/${apiKeyRes.data.api_key.id}/sales-channels/batch/add`,
{
sales_channel_ids: [sales_channel.id],
},
adminHeaders
)
.catch((err) => err)
expect(errorRes.response.status).toEqual(400)
expect(errorRes.response.data.message).toEqual(
"Sales channels can only be associated with publishable API keys"
)
})
it("should throw if sales channel does not exist", async () => {
const apiKeyRes = await api.post(
`/admin/api-keys`,
{
title: "Test publishable KEY",
type: ApiKeyType.PUBLISHABLE,
},
adminHeaders
)
const errorRes = await api
.post(
`/admin/api-keys/${apiKeyRes.data.api_key.id}/sales-channels/batch/add`,
{
sales_channel_ids: ["phony"],
},
adminHeaders
)
.catch((err) => err)
expect(errorRes.response.status).toEqual(400)
expect(errorRes.response.data.message).toEqual(
"Sales channels with IDs phony do not exist"
)
})
})
},
})

View File

@@ -1,181 +0,0 @@
import { ApiKeyType } from "@medusajs/utils"
import { IRegionModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { createAdminUser } from "../../../../helpers/create-admin-user"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, getContainer, api }) => {
describe("API Keys - Admin", () => {
let regionService: IRegionModuleService
let container
beforeAll(async () => {
container = getContainer()
regionService = container.resolve(
ModuleRegistrationName.REGION
) as IRegionModuleService
})
beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, container)
})
afterEach(async () => {
// TODO: Once teardown doesn't skip constraint checks and cascades, we can remove this
const existingRegions = await regionService.list({})
await regionService.delete(existingRegions.map((r) => r.id))
})
it("should correctly implement the entire lifecycle of an api key", async () => {
const created = await api.post(
`/admin/api-keys`,
{
title: "Test Secret Key",
type: ApiKeyType.SECRET,
},
adminHeaders
)
expect(created.status).toEqual(200)
expect(created.data.apiKey).toEqual(
expect.objectContaining({
id: created.data.apiKey.id,
title: "Test Secret Key",
created_by: "admin_user",
})
)
// On create we get the token in raw form so we can store it.
expect(created.data.apiKey.token).toContain("sk_")
const updated = await api.post(
`/admin/api-keys/${created.data.apiKey.id}`,
{
title: "Updated Secret Key",
},
adminHeaders
)
expect(updated.status).toEqual(200)
expect(updated.data.apiKey).toEqual(
expect.objectContaining({
id: created.data.apiKey.id,
title: "Updated Secret Key",
})
)
const revoked = await api.post(
`/admin/api-keys/${created.data.apiKey.id}/revoke`,
{},
adminHeaders
)
expect(revoked.status).toEqual(200)
expect(revoked.data.apiKey).toEqual(
expect.objectContaining({
id: created.data.apiKey.id,
revoked_by: "admin_user",
})
)
expect(revoked.data.apiKey.revoked_at).toBeTruthy()
const deleted = await api.delete(
`/admin/api-keys/${created.data.apiKey.id}`,
adminHeaders
)
const listedApiKeys = await api.get(`/admin/api-keys`, adminHeaders)
expect(deleted.status).toEqual(200)
expect(listedApiKeys.data.apiKeys).toHaveLength(0)
})
it("can use a secret api key for authentication", async () => {
const created = await api.post(
`/admin/api-keys`,
{
title: "Test Secret Key",
type: ApiKeyType.SECRET,
},
adminHeaders
)
const createdRegion = await api.post(
`/admin/regions`,
{
name: "Test Region",
currency_code: "usd",
countries: ["us", "ca"],
},
{
auth: {
username: created.data.apiKey.token,
},
}
)
expect(createdRegion.status).toEqual(200)
expect(createdRegion.data.region.name).toEqual("Test Region")
})
it("falls back to other mode of authentication when an api key is not valid", async () => {
const created = await api.post(
`/admin/api-keys`,
{
title: "Test Secret Key",
type: ApiKeyType.SECRET,
},
adminHeaders
)
await api.post(
`/admin/api-keys/${created.data.apiKey.id}/revoke`,
{},
adminHeaders
)
const err = await api
.post(
`/admin/regions`,
{
name: "Test Region",
currency_code: "usd",
countries: ["us", "ca"],
},
{
auth: {
username: created.data.apiKey.token,
},
}
)
.catch((e) => e.message)
const createdRegion = await api.post(
`/admin/regions`,
{
name: "Test Region",
currency_code: "usd",
countries: ["us", "ca"],
},
{
auth: {
username: created.data.apiKey.token,
},
...adminHeaders,
}
)
expect(err).toEqual("Request failed with status code 401")
expect(createdRegion.status).toEqual(200)
expect(createdRegion.data.region.name).toEqual("Test Region")
})
})
},
})

View File

@@ -3,7 +3,9 @@ import { ModuleJoinerConfig } from "@medusajs/types"
import { MapToConfig } from "@medusajs/utils"
import ApiKey from "./models/api-key"
export const LinkableKeys: Record<string, string> = {}
export const LinkableKeys: Record<string, string> = {
api_key_id: ApiKey.name,
}
const entityLinkableKeysMap: MapToConfig = {}
Object.entries(LinkableKeys).forEach(([key, value]) => {

View File

@@ -6,10 +6,10 @@ import {
import {
BeforeCreate,
Entity,
Enum,
OnInit,
PrimaryKey,
Property,
Enum,
} from "@mikro-orm/core"
const TypeIndex = createPsqlIndexStatementHelper({

View File

@@ -0,0 +1,47 @@
import { Modules } from "@medusajs/modules-sdk"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
links: {
api_key_id: string
sales_channel_ids: string[]
}[]
}
export const associateApiKeysWithSalesChannelsStepId =
"associate-sales-channels-with-api-keys"
export const associateApiKeysWithSalesChannelsStep = createStep(
associateApiKeysWithSalesChannelsStepId,
async (input: StepInput, { container }) => {
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
const links = input.links
.map((link) => {
return link.sales_channel_ids.map((id) => {
return {
[Modules.API_KEY]: {
publishable_key_id: link.api_key_id,
},
[Modules.SALES_CHANNEL]: {
sales_channel_id: id,
},
}
})
})
.flat()
const createdLinks = await remoteLink.create(links)
return new StepResponse(createdLinks, links)
},
async (links, { container }) => {
if (!links) {
return
}
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
await remoteLink.dismiss(links)
}
)

View File

@@ -1,4 +1,6 @@
export * from "./associate-sales-channels-with-publishable-keys"
export * from "./create-api-keys"
export * from "./delete-api-keys"
export * from "./update-api-keys"
export * from "./revoke-api-keys"
export * from "./update-api-keys"
export * from "./validate-sales-channel-exists"

View File

@@ -0,0 +1,37 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ISalesChannelModuleService } from "@medusajs/types"
import { MedusaError, arrayDifference } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
sales_channel_ids: string[]
}
export const validateSalesChannelsExistStepId = "validate-sales-channels-exist"
export const validateSalesChannelsExistStep = createStep(
validateSalesChannelsExistStepId,
async (data: StepInput, { container }) => {
const salesChannelModuleService =
container.resolve<ISalesChannelModuleService>(
ModuleRegistrationName.SALES_CHANNEL
)
const salesChannels = await salesChannelModuleService.list(
{ id: data.sales_channel_ids },
{ select: ["id"] }
)
const salesChannelIds = salesChannels.map((v) => v.id)
const notFound = arrayDifference(data.sales_channel_ids, salesChannelIds)
if (notFound.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Sales channels with IDs ${notFound.join(", ")} do not exist`
)
}
return new StepResponse(salesChannelIds)
}
)

View File

@@ -0,0 +1,31 @@
import {
WorkflowData,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import {
associateApiKeysWithSalesChannelsStep,
validateSalesChannelsExistStep,
} from "../steps"
type WorkflowInput = {
data: {
api_key_id: string
sales_channel_ids: string[]
}[]
}
export const addSalesChannelsToApiKeyWorkflowId =
"add-sales-channels-to-api-key"
export const addSalesChannelsToApiKeyWorkflow = createWorkflow(
addSalesChannelsToApiKeyWorkflowId,
(input: WorkflowData<WorkflowInput>) => {
const salesChannelIds = transform(input.data, (data) =>
data.map((d) => d.sales_channel_ids).flat()
)
validateSalesChannelsExistStep({
sales_channel_ids: salesChannelIds,
})
associateApiKeysWithSalesChannelsStep({ links: input.data })
}
)

View File

@@ -2,3 +2,4 @@ export * from "./create-api-keys"
export * from "./delete-api-keys"
export * from "./update-api-keys"
export * from "./revoke-api-keys"
export * from "./add-sales-channels-to-publishable-key"

View File

@@ -1,6 +1,6 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "../links"
import { Modules } from "@medusajs/modules-sdk"
export const PublishableApiKeySalesChannel: ModuleJoinerConfig = {
serviceName: LINKS.PublishableApiKeySalesChannel,
@@ -21,14 +21,12 @@ export const PublishableApiKeySalesChannel: ModuleJoinerConfig = {
relationships: [
{
serviceName: Modules.API_KEY,
isInternalService: true,
primaryKey: "id",
foreignKey: "publishable_key_id",
alias: "api_key",
},
{
serviceName: Modules.SALES_CHANNEL,
isInternalService: true,
primaryKey: "id",
foreignKey: "sales_channel_id",
alias: "sales_channel",
@@ -42,7 +40,6 @@ export const PublishableApiKeySalesChannel: ModuleJoinerConfig = {
},
relationship: {
serviceName: LINKS.PublishableApiKeySalesChannel,
isInternalService: true,
primaryKey: "publishable_key_id",
foreignKey: "id",
alias: "sales_channels_link",
@@ -56,7 +53,6 @@ export const PublishableApiKeySalesChannel: ModuleJoinerConfig = {
},
relationship: {
serviceName: LINKS.PublishableApiKeySalesChannel,
isInternalService: true,
primaryKey: "sales_channel_id",
foreignKey: "id",
alias: "api_keys_link",

View File

@@ -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 })
}

View File

@@ -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 (

View File

@@ -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 })
}

View File

@@ -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),
],
},
]

View File

@@ -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,
}

View File

@@ -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] })
}

View File

@@ -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[]
}

View File

@@ -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>,

View File

@@ -1,3 +1 @@
export * as publishableApiKey from "./publishable-api-key-service"
export * as shippingProfile from "./shipping-profile-service"

View File

@@ -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