feat(medusa): PublishableApiKeys CRUD (#2567)

This commit is contained in:
Frane Polić
2022-11-14 19:30:24 +01:00
committed by GitHub
parent d2b1848e52
commit d0809bdf25
15 changed files with 1036 additions and 4 deletions

View File

@@ -0,0 +1,214 @@
const path = require("path")
const { IdMap } = require("medusa-test-utils")
const startServerWithEnvironment =
require("../../../helpers/start-server-with-environment").default
const { useApi } = require("../../../helpers/use-api")
const { useDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder")
const {
simplePublishableApiKeyFactory,
} = require("../../factories/simple-publishable-api-key-factory")
jest.setTimeout(50000)
const adminHeaders = {
headers: {
Authorization: "Bearer test_token",
},
}
describe("[MEDUSA_FF_PUBLISHABLE_API_KEYS] Publishable API keys", () => {
let medusaProcess
let dbConnection
const adminUserId = "admin_user"
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
const [process, connection] = await startServerWithEnvironment({
cwd,
env: { MEDUSA_FF_PUBLISHABLE_API_KEYS: true },
verbose: false,
})
dbConnection = connection
medusaProcess = process
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("GET /admin/publishable-api-keys/:id", () => {
const pubKeyId = IdMap.getId("pubkey-get-id")
beforeEach(async () => {
await adminSeeder(dbConnection)
await simplePublishableApiKeyFactory(dbConnection, {
id: pubKeyId,
created_by: adminUserId,
})
})
afterEach(async () => {
const db = useDb()
return await db.teardown()
})
it("retrieve a publishable key by id ", async () => {
const api = useApi()
const response = await api.get(
`/admin/publishable-api-keys/${pubKeyId}`,
adminHeaders
)
expect(response.status).toBe(200)
expect(response.data.publishable_api_key).toMatchObject({
id: pubKeyId,
created_at: expect.any(String),
updated_at: expect.any(String),
created_by: adminUserId,
revoked_by: null,
revoked_at: null,
})
})
})
describe("GET /admin/publishable-api-keys", () => {
beforeEach(async () => {
await adminSeeder(dbConnection)
await simplePublishableApiKeyFactory(dbConnection, {})
await simplePublishableApiKeyFactory(dbConnection, {})
await simplePublishableApiKeyFactory(dbConnection, {})
})
afterEach(async () => {
const db = useDb()
return await db.teardown()
})
it("list publishable keys", async () => {
const api = useApi()
const response = await api.get(
`/admin/publishable-api-keys?limit=2`,
adminHeaders
)
expect(response.data.count).toBe(3)
expect(response.data.limit).toBe(2)
expect(response.data.offset).toBe(0)
expect(response.data.publishable_api_keys).toHaveLength(2)
})
})
describe("POST /admin/publishable-api-keys", () => {
beforeEach(async () => {
await adminSeeder(dbConnection)
})
afterEach(async () => {
const db = useDb()
return await db.teardown()
})
it("crete a publishable keys", async () => {
const api = useApi()
const response = await api.post(
`/admin/publishable-api-keys`,
{},
adminHeaders
)
expect(response.status).toBe(200)
expect(response.data.publishable_api_key).toMatchObject({
created_by: "admin_user",
id: expect.any(String),
revoked_by: null,
revoked_at: null,
created_at: expect.any(String),
updated_at: expect.any(String),
})
})
})
describe("POST /admin/publishable-api-keys/:id/revoke", () => {
const pubKeyId = IdMap.getId("pubkey-get-id")
beforeEach(async () => {
await adminSeeder(dbConnection)
await simplePublishableApiKeyFactory(dbConnection, {
id: pubKeyId,
})
})
afterEach(async () => {
const db = useDb()
return await db.teardown()
})
it("revoke a publishable key", async () => {
const api = useApi()
const response = await api.post(
`/admin/publishable-api-keys/${pubKeyId}/revoke`,
{},
adminHeaders
)
expect(response.status).toBe(200)
expect(response.data.publishable_api_key).toMatchObject({
id: pubKeyId,
created_at: expect.any(String),
updated_at: expect.any(String),
revoked_by: adminUserId,
revoked_at: expect.any(String),
})
})
})
describe("DELETE /admin/publishable-api-keys/:id", () => {
const pubKeyId = IdMap.getId("pubkey-get-id")
beforeEach(async () => {
await adminSeeder(dbConnection)
await simplePublishableApiKeyFactory(dbConnection, {
id: pubKeyId,
})
})
afterEach(async () => {
const db = useDb()
return await db.teardown()
})
it("delete a publishable key", async () => {
const api = useApi()
const response1 = await api.delete(
`/admin/publishable-api-keys/${pubKeyId}`,
adminHeaders
)
expect(response1.status).toBe(200)
expect(response1.data).toEqual({
id: pubKeyId,
object: "publishable_api_key",
deleted: true,
})
try {
await api.get(`/admin/publishable-api-keys/${pubKeyId}`, adminHeaders)
} catch (e) {
expect(e.response.status).toBe(404)
}
})
})
})

View File

@@ -0,0 +1,20 @@
import { Connection } from "typeorm"
import { PublishableApiKey } from "@medusajs/medusa"
export type PublishableApiKeyData = {
id?: string
revoked_at?: Date
revoked_by?: string
created_by?: string
}
export const simplePublishableApiKeyFactory = async (
connection: Connection,
data: PublishableApiKeyData = {}
): Promise<PublishableApiKey> => {
const manager = connection.manager
const pubKey = manager.create(PublishableApiKey, data)
return await manager.save(pubKey)
}

View File

@@ -37,6 +37,7 @@ export * from "./routes/admin/price-lists"
export * from "./routes/admin/product-tags"
export * from "./routes/admin/product-types"
export * from "./routes/admin/products"
export * from "./routes/admin/publishable-api-keys"
export * from "./routes/admin/regions"
export * from "./routes/admin/return-reasons"
export * from "./routes/admin/returns"

View File

@@ -20,6 +20,7 @@ import orderRoutes from "./orders"
import priceListRoutes from "./price-lists"
import productTagRoutes from "./product-tags"
import productTypesRoutes from "./product-types"
import publishableApiKeyRoutes from "./publishable-api-keys"
import productRoutes from "./products"
import regionRoutes from "./regions"
import returnReasonRoutes from "./return-reasons"
@@ -89,6 +90,7 @@ export default (app, container, config) => {
productRoutes(route, featureFlagRouter)
productTagRoutes(route)
productTypesRoutes(route)
publishableApiKeyRoutes(route)
regionRoutes(route, featureFlagRouter)
returnReasonRoutes(route)
returnRoutes(route)

View File

@@ -6,8 +6,8 @@ import { IsOptional, IsString } from "class-validator"
/**
* @oas [get] /order-edits
* operationId: "GetOrderEdits"
* summary: "List an OrderEdit"
* description: "List a OrderEdit."
* summary: "List OrderEdits"
* description: "List OrderEdits."
* x-authenticated: true
* parameters:
* - (query) q {string} Query used for searching order edit internal note.

View File

@@ -0,0 +1,72 @@
import { Request, Response } from "express"
import { EntityManager } from "typeorm"
import PublishableApiKeyService from "../../../../services/publishable-api-key"
/**
* @oas [post] /publishable-api-keys
* operationId: "PostPublishableApiKeys"
* summary: "Create a PublishableApiKey"
* description: "Creates a PublishableApiKey."
* x-authenticated: true
* x-codeSamples:
* - lang: JavaScript
* label: JS Client
* source: |
* import Medusa from "@medusajs/medusa-js"
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
* // must be previously logged in or use api token
* medusa.admin.publishableApiKey.create()
* .then(({ publishable_api_key }) => {
* console.log(publishable_api_key.id)
* })
* - lang: Shell
* label: cURL
* source: |
* curl --location --request POST 'https://medusa-url.com/admin/publishable-api-keys' \
* --header 'Authorization: Bearer {api_token}'
* -d '{ "created_by": "user_123" }'
* security:
* - api_token: []
* - cookie_auth: []
* tags:
* - PublishableApiKey
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* publishable_api_key:
* $ref: "#/components/schemas/publishable_api_key"
* "400":
* $ref: "#/components/responses/400_error"
* "401":
* $ref: "#/components/responses/unauthorized"
* "404":
* $ref: "#/components/responses/not_found_error"
* "409":
* $ref: "#/components/responses/invalid_state_error"
* "422":
* $ref: "#/components/responses/invalid_request_error"
* "500":
* $ref: "#/components/responses/500_error"
*/
export default async (req: Request, res: Response) => {
const publishableApiKeyService = req.scope.resolve(
"publishableApiKeyService"
) as PublishableApiKeyService
const manager = req.scope.resolve("manager") as EntityManager
const loggedInUserId = (req.user?.id ?? req.user?.userId) as string
const pubKey = await manager.transaction(async (transactionManager) => {
return await publishableApiKeyService
.withTransaction(transactionManager)
.create({ loggedInUserId })
})
return res.status(200).json({ publishable_api_key: pubKey })
}

View File

@@ -0,0 +1,76 @@
import { EntityManager } from "typeorm"
import PublishableApiKeyService from "../../../../services/publishable-api-key"
/**
* @oas [delete] /publishable-api-keys/{id}
* operationId: "DeletePublishableApiKeysPublishableApiKey"
* summary: "Delete a PublishableApiKey"
* description: "Deletes a PublishableApiKeys"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The ID of the PublishableApiKeys to delete.
* x-codeSamples:
* - lang: JavaScript
* label: JS Client
* source: |
* import Medusa from "@medusajs/medusa-js"
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
* // must be previously logged in or use api token
* medusa.admin.publishableApiKey.delete(key_id)
* .then(({ id, object, deleted }) => {
* console.log(id)
* })
* - lang: Shell
* label: cURL
* source: |
* curl --location --request DELETE 'https://medusa-url.com/admin/publishable-api-key/{id}' \
* --header 'Authorization: Bearer {api_token}'
* security:
* - api_token: []
* - cookie_auth: []
* tags:
* - PublishableApiKey
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* id:
* type: string
* description: The ID of the deleted PublishableApiKey.
* object:
* type: string
* description: The type of the object that was deleted.
* format: publishable_api_key
* deleted:
* type: boolean
* description: Whether the PublishableApiKeys was deleted.
* default: true
* "400":
* $ref: "#/components/responses/400_error"
*/
export default async (req, res) => {
const { id } = req.params
const publishableApiKeyService: PublishableApiKeyService = req.scope.resolve(
"publishableApiKeyService"
)
const manager: EntityManager = req.scope.resolve("manager")
await manager.transaction(async (transactionManager) => {
await publishableApiKeyService
.withTransaction(transactionManager)
.delete(id)
})
res.status(200).send({
id,
object: "publishable_api_key",
deleted: true,
})
}

View File

@@ -0,0 +1,67 @@
import { Request, Response } from "express"
import PublishableApiKeyService from "../../../../services/publishable-api-key"
/**
* @oas [get] /publishable-api-keys/{id}
* operationId: "GetPublishableApiKeysPublishableApiKey"
* summary: "Get a Publishable API Key"
* description: "Retrieve the Publishable Api Key."
* parameters:
* - (path) id=* {string} The ID of the PublishableApiKey.
* x-authenticated: true
* x-codeSamples:
* - lang: JavaScript
* label: JS Client
* source: |
* import Medusa from "@medusajs/medusa-js"
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
* // must be previously logged in or use api token
* medusa.admin.publishableApiKey.retrieve(pubKeyId)
* .then(({ publishable_api_key }) => {
* console.log(publishable_api_key.id)
* })
* - lang: Shell
* label: cURL
* source: |
* curl --location --request GET 'https://medusa-url.com/admin/publishable-api-keys/pubkey_123' \
* --header 'Authorization: Bearer {api_token}'
* -d '{ "created_by": "user_123" }'
* security:
* - api_token: []
* - cookie_auth: []
* tags:
* - PublishableApiKey
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* publishable_api_key:
* $ref: "#/components/schemas/publishable_api_key"
* "400":
* $ref: "#/components/responses/400_error"
* "401":
* $ref: "#/components/responses/unauthorized"
* "404":
* $ref: "#/components/responses/not_found_error"
* "409":
* $ref: "#/components/responses/invalid_state_error"
* "422":
* $ref: "#/components/responses/invalid_request_error"
* "500":
* $ref: "#/components/responses/500_error"
*/
export default async (req: Request, res: Response) => {
const { id } = req.params
const publishableApiKeyService = req.scope.resolve(
"publishableApiKeyService"
) as PublishableApiKeyService
const pubKey = await publishableApiKeyService.retrieve(id)
return res.json({ publishable_api_key: pubKey })
}

View File

@@ -0,0 +1,56 @@
import { Router } from "express"
import { isFeatureFlagEnabled } from "../../../middlewares/feature-flag-enabled"
import PublishableAPIKeysFeatureFlag from "../../../../loaders/feature-flags/publishable-api-keys"
import middlewares, { transformQuery } from "../../../middlewares"
import { GetPublishableApiKeysParams } from "./list-publishable-api-keys"
import { PublishableApiKey } from "../../../../models"
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
const route = Router()
export default (app) => {
app.use(
"/publishable-api-keys",
isFeatureFlagEnabled(PublishableAPIKeysFeatureFlag.key),
route
)
route.post(
"/",
middlewares.wrap(require("./create-publishable-api-key").default)
)
route.get(
"/:id",
middlewares.wrap(require("./get-publishable-api-key").default)
)
route.delete(
"/:id",
middlewares.wrap(require("./delete-publishable-api-key").default)
)
route.post(
"/:id/revoke",
middlewares.wrap(require("./revoke-publishable-api-key").default)
)
route.get(
"/",
transformQuery(GetPublishableApiKeysParams, {
isList: true,
}),
middlewares.wrap(require("./list-publishable-api-keys").default)
)
}
export type AdminPublishableApiKeysRes = {
publishable_api_key: PublishableApiKey
}
export type AdminPublishableApiKeysListRes = PaginatedResponse & {
publishable_api_keys: PublishableApiKey[]
}
export type AdminPublishableApiKeyDeleteRes = DeleteResponse
export * from "./list-publishable-api-keys"

View File

@@ -0,0 +1,87 @@
import { Request, Response } from "express"
import { IsOptional, IsString } from "class-validator"
import { extendedFindParamsMixin } from "../../../../types/common"
import PublishableApiKeyService from "../../../../services/publishable-api-key"
/**
* @oas [get] /publishable-api-keys
* operationId: "GetPublishableApiKeys"
* summary: "List PublishableApiKeys"
* description: "List PublishableApiKeys."
* x-authenticated: true
* parameters:
* - (query) order_id {string} List publishable keys by id.
* - (query) limit=20 {number} The number of items in the response
* - (query) offset=0 {number} The offset of items in response
* - (query) expand {string} Comma separated list of relations to include in the results.
* - (query) fields {string} Comma separated list of fields to include in the results.
* x-codeSamples:
* - lang: JavaScript
* label: JS Client
* source: |
* import Medusa from "@medusajs/medusa-js"
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
* // must be previously logged in or use api token
* medusa.admin.publishableApiKeys.list()
* .then(({ publishable_api_keys }) => {
* console.log(publishable_api_keys)
* })
* - lang: Shell
* label: cURL
* source: |
* curl --location --request GET 'https://medusa-url.com/admin/publishable-api-keys' \
* --header 'Authorization: Bearer {api_token}'
* security:
* - api_token: []
* - cookie_auth: []
* tags:
* - PublishableApiKeys
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* publishable_api_keys:
* type: array
* $ref: "#/components/schemas/publishable_api_key"
* "400":
* $ref: "#/components/responses/400_error"
* "401":
* $ref: "#/components/responses/unauthorized"
* "404":
* $ref: "#/components/responses/not_found_error"
* "409":
* $ref: "#/components/responses/invalid_state_error"
* "422":
* $ref: "#/components/responses/invalid_request_error"
* "500":
* $ref: "#/components/responses/500_error"
*/
export default async (req: Request, res: Response) => {
const publishableApiKeyService: PublishableApiKeyService = req.scope.resolve(
"publishableApiKeyService"
)
const { filterableFields, listConfig } = req
const { skip, take } = listConfig
const [pubKeys, count] = await publishableApiKeyService.listAndCount(
filterableFields,
listConfig
)
return res.json({
publishable_api_keys: pubKeys,
count,
limit: take,
offset: skip,
})
}
export class GetPublishableApiKeysParams extends extendedFindParamsMixin({
limit: 20,
offset: 0,
}) {}

View File

@@ -0,0 +1,78 @@
import { Request, Response } from "express"
import { EntityManager } from "typeorm"
import PublishableApiKeyService from "../../../../services/publishable-api-key"
/**
* @oas [post] /publishable-api-keys/{id}/revoke
* operationId: "PostPublishableApiKeysPublishableApiKeyRevoke"
* summary: "Revoke a PublishableApiKey"
* description: "Revokes a PublishableApiKey."
* parameters:
* - (path) id=* {string} The ID of the PublishableApiKey.
* x-authenticated: true
* x-codeSamples:
* - lang: JavaScript
* label: JS Client
* source: |
* import Medusa from "@medusajs/medusa-js"
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
* // must be previously logged in or use api token
* medusa.admin.publishableApiKey.revoke()
* .then(({ publishable_api_key }) => {
* console.log(publishable_api_key.id)
* })
* - lang: Shell
* label: cURL
* source: |
* curl --location --request POST 'https://medusa-url.com/admin/publishable-api-keys/pubkey_123/revoke' \
* --header 'Authorization: Bearer {api_token}'
* -d '{ "created_by": "user_123", "revoked_by": "user_123" }'
* security:
* - api_token: []
* - cookie_auth: []
* tags:
* - PublishableApiKey
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* publishable_api_key:
* $ref: "#/components/schemas/publishable_api_key"
* "400":
* $ref: "#/components/responses/400_error"
* "401":
* $ref: "#/components/responses/unauthorized"
* "404":
* $ref: "#/components/responses/not_found_error"
* "409":
* $ref: "#/components/responses/invalid_state_error"
* "422":
* $ref: "#/components/responses/invalid_request_error"
* "500":
* $ref: "#/components/responses/500_error"
*/
export default async (req: Request, res: Response) => {
const { id } = req.params
const publishableApiKeyService = req.scope.resolve(
"publishableApiKeyService"
) as PublishableApiKeyService
const manager = req.scope.resolve("manager") as EntityManager
const loggedInUserId = (req.user?.id ?? req.user?.userId) as string
const pubKey = await manager.transaction(async (transactionManager) => {
const publishableApiKeyServiceTx =
publishableApiKeyService.withTransaction(transactionManager)
await publishableApiKeyServiceTx.revoke(id, { loggedInUserId })
return await publishableApiKeyServiceTx.retrieve(id)
})
return res.json({ publishable_api_key: pubKey })
}

View File

@@ -1,6 +1,71 @@
import { EntityRepository, Repository } from "typeorm"
import { flatten, groupBy, merge } from "lodash"
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
import { PublishableApiKey } from "../models/publishable-api-key"
@EntityRepository(PublishableApiKey)
export class PublishableApiKeyRepository extends Repository<PublishableApiKey> {}
export class PublishableApiKeyRepository extends Repository<PublishableApiKey> {
public async findWithRelations(
relations: (keyof PublishableApiKey | string)[] = [],
idsOrOptionsWithoutRelations:
| Omit<FindManyOptions<PublishableApiKey>, "relations">
| string[] = {}
): Promise<[PublishableApiKey[], number]> {
let entities: PublishableApiKey[] = []
let count = 0
if (Array.isArray(idsOrOptionsWithoutRelations)) {
entities = await this.findByIds(idsOrOptionsWithoutRelations)
count = idsOrOptionsWithoutRelations.length
} else {
const [results, resultCount] = await this.findAndCount(
idsOrOptionsWithoutRelations
)
entities = results
count = resultCount
}
const entitiesIds = entities.map(({ id }) => id)
const groupedRelations = {}
for (const rel of relations) {
const [topLevel] = rel.split(".")
if (groupedRelations[topLevel]) {
groupedRelations[topLevel].push(rel)
} else {
groupedRelations[topLevel] = [rel]
}
}
const entitiesIdsWithRelations = await Promise.all(
Object.entries(groupedRelations).map(async ([_, rels]) => {
return this.findByIds(entitiesIds, {
select: ["id"],
relations: rels as string[],
})
})
).then(flatten)
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return [
Object.values(entitiesAndRelationsById).map((v) => merge({}, ...v)),
count,
]
}
public async findOneWithRelations(
relations: Array<keyof PublishableApiKey> = [],
optionsWithoutRelations: Omit<
FindManyOptions<PublishableApiKey>,
"relations"
> = {}
): Promise<PublishableApiKey> {
// Limit 1
optionsWithoutRelations.take = 1
const [result] = await this.findWithRelations(
relations,
optionsWithoutRelations
)
return result[0]
}
}

View File

@@ -0,0 +1,83 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import { EventBusService } from "../index"
import { EventBusServiceMock } from "../__mocks__/event-bus"
import PublishableApiKeyService from "../publishable-api-key"
const pubKeyToRetrieve = {
id: IdMap.getId("pub-key-to-retrieve"),
created_at: new Date(),
created_by: IdMap.getId("admin_user"),
revoked_by: null,
revoked_at: null,
}
describe("PublishableApiKeyService", () => {
afterEach(() => {
jest.clearAllMocks()
})
const publishableApiKeyRepository = MockRepository({
findOneWithRelations: (data) => ({ ...pubKeyToRetrieve, ...data }),
create: (data) => {
return {
...pubKeyToRetrieve,
...data,
}
},
})
const publishableApiKeyService = new PublishableApiKeyService({
manager: MockManager,
publishableApiKeyRepository: publishableApiKeyRepository,
eventBusService: EventBusServiceMock as unknown as EventBusService,
})
it("should retrieve a publishable api key and call the repository with the right arguments", async () => {
await publishableApiKeyService.retrieve(
IdMap.getId("order-edit-with-changes")
)
expect(
publishableApiKeyRepository.findOneWithRelations
).toHaveBeenCalledTimes(1)
expect(
publishableApiKeyRepository.findOneWithRelations
).toHaveBeenCalledWith(undefined, {
where: { id: IdMap.getId("order-edit-with-changes") },
})
})
it("should create a publishable api key and call the repository with the right arguments as well as the event bus service", async () => {
await publishableApiKeyService.create({
loggedInUserId: IdMap.getId("admin_user"),
})
expect(publishableApiKeyRepository.create).toHaveBeenCalledTimes(1)
expect(publishableApiKeyRepository.create).toHaveBeenCalledWith({
created_by: IdMap.getId("admin_user"),
})
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
PublishableApiKeyService.Events.CREATED,
{ id: expect.any(String) }
)
})
it("should revoke a publishable api key", async () => {
await publishableApiKeyService.revoke("id", {
loggedInUserId: IdMap.getId("admin_user"),
})
expect(publishableApiKeyRepository.save).toHaveBeenLastCalledWith(
expect.objectContaining({
revoked_by: IdMap.getId("admin_user"),
})
)
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
PublishableApiKeyService.Events.REVOKED,
{ id: expect.any(String) }
)
})
})

View File

@@ -35,6 +35,7 @@ export { default as ProductService } from "./product"
export { default as ProductCollectionService } from "./product-collection"
export { default as ProductTypeService } from "./product-type"
export { default as ProductVariantService } from "./product-variant"
import { default as PublishableApiKey } from "./publishable-api-key"
export { default as RegionService } from "./region"
export { default as ReturnService } from "./return"
export { default as ReturnReasonService } from "./return-reason"

View File

@@ -0,0 +1,210 @@
import { EntityManager } from "typeorm"
import { MedusaError } from "medusa-core-utils"
import { PublishableApiKeyRepository } from "../repositories/publishable-api-key"
import { FindConfig, Selector } from "../types/common"
import { PublishableApiKey } from "../models"
import { TransactionBaseService } from "../interfaces"
import EventBusService from "./event-bus"
import { buildQuery } from "../utils"
type InjectedDependencies = {
manager: EntityManager
eventBusService: EventBusService
publishableApiKeyRepository: typeof PublishableApiKeyRepository
}
/**
* A service for PublishableApiKey business logic.
*/
class PublishableApiKeyService extends TransactionBaseService {
static Events = {
CREATED: "publishable_api_key.created",
REVOKED: "publishable_api_key.revoked",
}
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
protected readonly eventBusService_: EventBusService
protected readonly publishableApiKeyRepository_: typeof PublishableApiKeyRepository
constructor({
manager,
eventBusService,
publishableApiKeyRepository,
}: InjectedDependencies) {
super(arguments[0])
this.manager_ = manager
this.eventBusService_ = eventBusService
this.publishableApiKeyRepository_ = publishableApiKeyRepository
}
/**
* Create a PublishableApiKey record.
*
* @params context - key creation context object
*/
async create(context: {
loggedInUserId: string
}): Promise<PublishableApiKey | never> {
return await this.atomicPhase_(async (manager) => {
const publishableApiKeyRepo = manager.getCustomRepository(
this.publishableApiKeyRepository_
)
const publishableApiKey = publishableApiKeyRepo.create({
created_by: context.loggedInUserId,
})
await this.eventBusService_
.withTransaction(manager)
.emit(PublishableApiKeyService.Events.CREATED, {
id: publishableApiKey.id,
})
return await publishableApiKeyRepo.save(publishableApiKey)
})
}
/**
* Retrieves a PublishableApiKey by id
*
* @param publishableApiKeyId - id of the key
* @param config - a find config object
*/
async retrieve(
publishableApiKeyId: string,
config: FindConfig<PublishableApiKey> = {}
): Promise<PublishableApiKey | never> {
return await this.retrieve_({ id: publishableApiKeyId }, config)
}
/**
* Generic retrieve for selecting PublishableApiKEys by different attributes.
*
* @param selector - a PublishableApiKey selector object
* @param config - a find config object
*/
protected async retrieve_(
selector: Selector<PublishableApiKey>,
config: FindConfig<PublishableApiKey> = {}
): Promise<PublishableApiKey | never> {
const repo = this.manager_.getCustomRepository(
this.publishableApiKeyRepository_
)
const { relations, ...query } = buildQuery(selector, config)
const publishableApiKey = await repo.findOneWithRelations(
relations as (keyof PublishableApiKey)[],
query
)
if (!publishableApiKey) {
const selectorConstraints = Object.entries(selector)
.map((key, value) => `${key}: ${value}`)
.join(", ")
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Publishable key with ${selectorConstraints} was not found`
)
}
return publishableApiKey
}
/**
* Lists publishable API keys based on the provided parameters.
*
* @return an array containing publishable API keys and a total count of records that matches the query
*/
async listAndCount(
selector: Selector<PublishableApiKey>,
config: FindConfig<PublishableApiKey> = {
skip: 0,
take: 20,
}
): Promise<[PublishableApiKey[], number]> {
const manager = this.manager_
const pubKeyRepo = manager.getCustomRepository(
this.publishableApiKeyRepository_
)
const query = buildQuery(selector, config)
return await pubKeyRepo.findAndCount(query)
}
/**
* Delete Publishable API key.
*
* @param publishableApiKeyId - id of the key being deleted
*/
async delete(publishableApiKeyId: string): Promise<void> {
return await this.atomicPhase_(async (manager) => {
const repo = manager.getCustomRepository(
this.publishableApiKeyRepository_
)
const publishableApiKey = await this.retrieve(publishableApiKeyId).catch()
if (publishableApiKey) {
await repo.remove(publishableApiKey)
}
})
}
/**
* Revoke a PublishableApiKey
*
* @param publishableApiKeyId - id of the key
* @param context - key revocation context object
*/
async revoke(
publishableApiKeyId: string,
context: {
loggedInUserId: string
}
): Promise<void | never> {
return await this.atomicPhase_(async (manager) => {
const repo = manager.getCustomRepository(
this.publishableApiKeyRepository_
)
const pubKey = await this.retrieve(publishableApiKeyId)
if (pubKey.revoked_at) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`PublishableApiKey has already been revoked.`
)
}
pubKey.revoked_at = new Date()
pubKey.revoked_by = context.loggedInUserId
await repo.save(pubKey)
await this.eventBusService_
.withTransaction(manager)
.emit(PublishableApiKeyService.Events.REVOKED, {
id: pubKey.id,
})
})
}
/**
* Check whether the key is active (i.e. haven't been revoked or deleted yet)
*
* @param publishableApiKeyId - id of the key
*/
async isValid(publishableApiKeyId: string): Promise<boolean> {
const pubKey = await this.retrieve(publishableApiKeyId)
return pubKey.revoked_by === null
}
}
export default PublishableApiKeyService