Skip to content

feat: seller api keys #127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions apps/backend/src/api/openapi/vendor/seller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,54 @@
* type: boolean
* description: Whether the invite has been accepted.
*/

/**
* @schema SellerApiKey
* title: "Api key"
* description: "A seller api key details"
* properties:
* id:
* type: string
* description: The unique identifier of the api key.
* title:
* type: string
* description: The api key title.
* redacted:
* type: string
* description: The redacted api key value.
* created_by:
* type: string
* description: The identity that created the api key.
* revoked_by:
* type: string
* description: The identity that revoked the api key.
* revoked_at:
* type: string
* format: date-time
* description: The date with timezone at which the invite expires.
*/

/**
* @schema SellerApiKeyExplicit
* title: "Api key explicit"
* description: "A seller api key with explicit token value"
* properties:
* id:
* type: string
* description: The unique identifier of the api key.
* title:
* type: string
* description: The api key title.
* redacted:
* type: string
* description: The redacted api key value.
* seller_id:
* type: string
* description: The seller id associated with the api key.
* token:
* type: string
* description: Explicit api key value.
* created_by:
* type: string
* description: The identity that created the api key.
*/
122 changes: 122 additions & 0 deletions apps/backend/src/api/vendor/api-keys/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {
AuthenticatedMedusaRequest,
MedusaResponse,
container
} from '@medusajs/framework'
import { ContainerRegistrationKeys } from '@medusajs/framework/utils'

import { revokeSellerApiKeyWorkflow } from '../../../../workflows/seller/workflows'

/**
* @oas [get] /vendor/api-keys/{id}
* operationId: "VendorGetSellerApiKeyById"
* summary: "Get an api key by id"
* description: "Retrieves an api key by id for the authenticated vendor."
* x-authenticated: true
* parameters:
* - in: path
* name: id
* required: true
* description: The ID of the API key.
* schema:
* type: string
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* api_key:
* $ref: "#/components/schemas/SellerApiKey"
* tags:
* - Seller
* security:
* - api_token: []
* - cookie_auth: []
*/
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const query = container.resolve(ContainerRegistrationKeys.QUERY)

const {
data: [api_key]
} = await query.graph({
entity: 'seller_api_key',
fields: [
'id',
'title',
'redacted',
'created_by',
'revoked_at',
'revoked_by'
],
filters: { id: req.params.id }
})

res.json({ api_key })
}

/**
* @oas [delete] /vendor/api-keys/{id}
* operationId: "VendorRevokeSellerApiKeyById"
* summary: "Revoke an api key by id"
* description: "Revokes an api key by id for the authenticated vendor."
* x-authenticated: true
* parameters:
* - in: path
* name: id
* required: true
* description: The ID of the API key.
* schema:
* type: string
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* api_key:
* $ref: "#/components/schemas/SellerApiKey"
* tags:
* - Seller
* security:
* - api_token: []
* - cookie_auth: []
*/
export const DELETE = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const query = container.resolve(ContainerRegistrationKeys.QUERY)

await revokeSellerApiKeyWorkflow.run({
container: req.scope,
input: {
id: req.params.id,
revoked_by: req.auth_context.actor_id
}
})

const {
data: [api_key]
} = await query.graph({
entity: 'seller_api_key',
fields: [
'id',
'title',
'redacted',
'created_by',
'revoked_at',
'revoked_by'
],
filters: { id: req.params.id }
})

res.json({ api_key })
}
37 changes: 37 additions & 0 deletions apps/backend/src/api/vendor/api-keys/middlewares.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
MiddlewareRoute,
validateAndTransformBody,
validateAndTransformQuery
} from '@medusajs/framework'

import {
checkResourceOwnershipByResourceId,
filterBySellerId
} from '../../../shared/infra/http/middlewares'
import {
VendorCreateSellerApiKey,
VendorGetSellerApiKeysParams
} from './validators'

export const vendorApiKeyMiddlewares: MiddlewareRoute[] = [
{
method: ['GET'],
matcher: '/vendor/api-keys',
middlewares: [
validateAndTransformQuery(VendorGetSellerApiKeysParams, {}),
filterBySellerId()
]
},
{
method: ['POST'],
matcher: '/vendor/api-keys',
middlewares: [validateAndTransformBody(VendorCreateSellerApiKey)]
},
{
method: ['DELETE', 'GET'],
matcher: '/vendor/api-keys/:id',
middlewares: [
checkResourceOwnershipByResourceId({ entryPoint: 'seller_api_key' })
]
}
]
115 changes: 115 additions & 0 deletions apps/backend/src/api/vendor/api-keys/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
AuthenticatedMedusaRequest,
MedusaResponse,
container
} from '@medusajs/framework'
import { ContainerRegistrationKeys } from '@medusajs/framework/utils'

import { fetchSellerByAuthContext } from '../../../shared/infra/http/utils'
import { createSellerApiKeyWorkflow } from '../../../workflows/seller/workflows'
import { VendorCreateSellerApiKeyType } from './validators'

/**
* @oas [get] /vendor/api-keys
* operationId: "VendorGetSellerMyApiKeys"
* summary: "Get api keys of the current seller"
* description: "Retrieves the api keys associated with the seller."
* x-authenticated: true
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* api_keys:
* type: array
* items:
* $ref: "#/components/schemas/SellerApiKey"
* count:
* type: integer
* description: The total number of items available
* offset:
* type: integer
* description: The number of items skipped before these items
* limit:
* type: integer
* description: The number of items per page
* tags:
* - Seller
* security:
* - api_token: []
* - cookie_auth: []
*/
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const query = container.resolve(ContainerRegistrationKeys.QUERY)

const { data: api_keys, metadata } = await query.graph({
entity: 'seller_api_key',
fields: [
'id',
'title',
'redacted',
'created_by',
'revoked_at',
'revoked_by'
],
filters: req.filterableFields
})

res.json({
api_keys,
count: metadata?.count,
skip: metadata?.skip,
take: metadata?.take
})
}

/**
* @oas [post] /vendor/api-keys
* operationId: "VendorCreateApiKey"
* summary: "Create seller api key"
* description: "Creates a seller api key"
* x-authenticated: true
* requestBody:
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/VendorCreateSellerApiKey"
* responses:
* "201":
* description: Created
* content:
* application/json:
* schema:
* type: object
* properties:
* api_key:
* $ref: "#/components/schemas/SellerApiKeyExplicit"
* tags:
* - Seller
* security:
* - api_token: []
* - cookie_auth: []
*/
export const POST = async (
req: AuthenticatedMedusaRequest<VendorCreateSellerApiKeyType>,
res: MedusaResponse
) => {
const seller = await fetchSellerByAuthContext(req.auth_context, req.scope)

const { result: api_key } = await createSellerApiKeyWorkflow.run({
container: req.scope,
input: {
...req.validatedBody,
seller_id: seller.id,
created_by: req.auth_context.actor_id
}
})

res.status(201).json({ api_key })
}
29 changes: 29 additions & 0 deletions apps/backend/src/api/vendor/api-keys/validators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { z } from 'zod'

import { createFindParams } from '@medusajs/medusa/api/utils/validators'

/**
* @schema VendorCreateSellerApiKey
* title: "Create api key"
* description: "A schema for the api key creation."
* x-resourceId: VendorCreateSellerApiKey
* type: object
* properties:
* title:
* type: string
* description: The title of the key
*/
export type VendorCreateSellerApiKeyType = z.infer<
typeof VendorCreateSellerApiKey
>
export const VendorCreateSellerApiKey = z.object({
title: z.string().max(50)
})

export type VendorGetSellerApiKeysParamsType = z.infer<
typeof VendorGetSellerApiKeysParams
>
export const VendorGetSellerApiKeysParams = createFindParams({
offset: 0,
limit: 50
})
7 changes: 2 additions & 5 deletions apps/backend/src/api/vendor/campaigns/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AuthenticatedMedusaRequest, MedusaResponse } from '@medusajs/framework'
import { ContainerRegistrationKeys } from '@medusajs/framework/utils'

import sellerCampaign from '../../../links/seller-campaign'
import { fetchSellerByAuthActorId } from '../../../shared/infra/http/utils'
import { fetchSellerByAuthContext } from '../../../shared/infra/http/utils'
import { createVendorCampaignWorkflow } from '../../../workflows/campaigns/workflows'
import { VendorCreateCampaignType } from './validators'

Expand Down Expand Up @@ -118,10 +118,7 @@ export const POST = async (
res: MedusaResponse
) => {
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
const seller = await fetchSellerByAuthActorId(
req.auth_context?.actor_id,
req.scope
)
const seller = await fetchSellerByAuthContext(req.auth_context, req.scope)

const { result } = await createVendorCampaignWorkflow.run({
container: req.scope,
Expand Down
Loading