mirror of
https://github.com/strapi/strapi.git
synced 2025-08-08 08:46:42 +00:00
Merge pull request #19778 from strapi/feat/create-many-release-actions
feat(content-releases): new create many release actions endpoint
This commit is contained in:
commit
e6eaa3d056
@ -1,8 +1,10 @@
|
||||
import { AlreadyOnReleaseError } from '../../services/validation';
|
||||
import releaseActionController from '../release-action';
|
||||
|
||||
const mockSanitizedQueryRead = jest.fn().mockResolvedValue({});
|
||||
const mockFindActions = jest.fn().mockResolvedValue({ results: [], pagination: {} });
|
||||
const mockSanitizeOutput = jest.fn((entry: { id: number; name: string }) => ({ id: entry.id }));
|
||||
const mockCreateAction = jest.fn();
|
||||
|
||||
jest.mock('../../utils', () => ({
|
||||
getService: jest.fn(() => ({
|
||||
@ -18,6 +20,7 @@ jest.mock('../../utils', () => ({
|
||||
displayName: 'contentTypeB',
|
||||
},
|
||||
})),
|
||||
createAction: mockCreateAction,
|
||||
})),
|
||||
getPermissionsChecker: jest.fn(() => ({
|
||||
sanitizedQuery: {
|
||||
@ -92,6 +95,85 @@ describe('Release Action controller', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('createMany', () => {
|
||||
beforeEach(() => {
|
||||
global.strapi = {
|
||||
db: {
|
||||
transaction: jest.fn((cb) => cb()),
|
||||
},
|
||||
};
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('creates multiple release actions', async () => {
|
||||
mockCreateAction.mockResolvedValue({ id: 1 });
|
||||
|
||||
const ctx: any = {
|
||||
params: {
|
||||
releaseId: 1,
|
||||
},
|
||||
request: {
|
||||
body: [
|
||||
{
|
||||
entry: {
|
||||
id: 1,
|
||||
contentType: 'api::contentTypeA.contentTypeA',
|
||||
},
|
||||
type: 'publish',
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
id: 2,
|
||||
contentType: 'api::contentTypeB.contentTypeB',
|
||||
},
|
||||
type: 'unpublish',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await releaseActionController.createMany(ctx);
|
||||
|
||||
expect(mockCreateAction).toHaveBeenCalledTimes(2);
|
||||
expect(ctx.body.data).toHaveLength(2);
|
||||
expect(ctx.body.meta.totalEntries).toBe(2);
|
||||
expect(ctx.body.meta.entriesAlreadyInRelease).toBe(0);
|
||||
});
|
||||
|
||||
it('should count already added entries and dont throw an error', async () => {
|
||||
mockCreateAction.mockRejectedValue(
|
||||
new AlreadyOnReleaseError(
|
||||
'Entry with id 1 and contentType api::contentTypeA.contentTypeA already exists in release with id 1'
|
||||
)
|
||||
);
|
||||
|
||||
const ctx: any = {
|
||||
params: {
|
||||
releaseId: 1,
|
||||
},
|
||||
request: {
|
||||
body: [
|
||||
{
|
||||
entry: {
|
||||
id: 1,
|
||||
contentType: 'api::contentTypeA.contentTypeA',
|
||||
},
|
||||
type: 'publish',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await releaseActionController.createMany(ctx);
|
||||
|
||||
expect(mockCreateAction).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.body.data).toHaveLength(0);
|
||||
expect(ctx.body.meta.totalEntries).toBe(1);
|
||||
expect(ctx.body.meta.entriesAlreadyInRelease).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('throws an error given bad request arguments', () => {
|
||||
const ctx = {
|
||||
|
@ -7,12 +7,14 @@ import {
|
||||
} from './validation/release-action';
|
||||
import type {
|
||||
CreateReleaseAction,
|
||||
CreateManyReleaseActions,
|
||||
GetReleaseActions,
|
||||
UpdateReleaseAction,
|
||||
DeleteReleaseAction,
|
||||
} from '../../../shared/contracts/release-actions';
|
||||
import { getService } from '../utils';
|
||||
import { RELEASE_ACTION_MODEL_UID } from '../constants';
|
||||
import { AlreadyOnReleaseError } from '../services/validation';
|
||||
|
||||
const releaseActionController = {
|
||||
async create(ctx: Koa.Context) {
|
||||
@ -29,6 +31,48 @@ const releaseActionController = {
|
||||
};
|
||||
},
|
||||
|
||||
async createMany(ctx: Koa.Context) {
|
||||
const releaseId: CreateManyReleaseActions.Request['params']['releaseId'] = ctx.params.releaseId;
|
||||
const releaseActionsArgs: CreateManyReleaseActions.Request['body'] = ctx.request.body;
|
||||
|
||||
await Promise.all(
|
||||
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
||||
);
|
||||
|
||||
const releaseService = getService('release', { strapi });
|
||||
|
||||
const releaseActions = await strapi.db.transaction(async () => {
|
||||
const releaseActions = await Promise.all(
|
||||
releaseActionsArgs.map(async (releaseActionArgs) => {
|
||||
try {
|
||||
const action = await releaseService.createAction(releaseId, releaseActionArgs);
|
||||
|
||||
return action;
|
||||
} catch (error) {
|
||||
// If the entry is already in the release, we don't want to throw an error, so we catch and ignore it
|
||||
if (error instanceof AlreadyOnReleaseError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return releaseActions;
|
||||
});
|
||||
|
||||
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
||||
|
||||
ctx.body = {
|
||||
data: newReleaseActions,
|
||||
meta: {
|
||||
entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
|
||||
totalEntries: releaseActions.length,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
async findMany(ctx: Koa.Context) {
|
||||
const releaseId: GetReleaseActions.Request['params']['releaseId'] = ctx.params.releaseId;
|
||||
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
||||
|
@ -17,6 +17,22 @@ export default {
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/:releaseId/actions/bulk',
|
||||
handler: 'release-action.createMany',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{
|
||||
name: 'admin::hasPermissions',
|
||||
config: {
|
||||
actions: ['plugin::content-releases.create-action'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/:releaseId/actions',
|
||||
|
@ -5,6 +5,13 @@ import type { Release, CreateRelease, UpdateRelease } from '../../../shared/cont
|
||||
import type { CreateReleaseAction } from '../../../shared/contracts/release-actions';
|
||||
import { RELEASE_MODEL_UID } from '../constants';
|
||||
|
||||
export class AlreadyOnReleaseError extends errors.ApplicationError<'AlreadyOnReleaseError'> {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'AlreadyOnReleaseError';
|
||||
}
|
||||
}
|
||||
|
||||
const createReleaseValidationService = ({ strapi }: { strapi: LoadedStrapi }) => ({
|
||||
async validateUniqueEntry(
|
||||
releaseId: CreateReleaseAction.Request['params']['releaseId'],
|
||||
@ -29,7 +36,7 @@ const createReleaseValidationService = ({ strapi }: { strapi: LoadedStrapi }) =>
|
||||
);
|
||||
|
||||
if (isEntryInRelease) {
|
||||
throw new errors.ValidationError(
|
||||
throw new AlreadyOnReleaseError(
|
||||
`Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
|
||||
);
|
||||
}
|
||||
|
@ -59,6 +59,34 @@ export declare namespace CreateReleaseAction {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /content-releases/:releaseId/actions/bulk - Create multiple release actions
|
||||
*/
|
||||
export declare namespace CreateManyReleaseActions {
|
||||
export interface Request {
|
||||
params: {
|
||||
releaseId: Release['id'];
|
||||
};
|
||||
body: Array<{
|
||||
type: ReleaseAction['type'];
|
||||
entry: {
|
||||
id: ReleaseActionEntry['id'];
|
||||
locale?: ReleaseActionEntry['locale'];
|
||||
contentType: Common.UID.ContentType;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
data: Array<ReleaseAction>;
|
||||
meta: {
|
||||
totalEntries: number;
|
||||
entriesAlreadyInRelease: number;
|
||||
};
|
||||
error?: errors.ApplicationError | errors.ValidationError | errors.NotFoundError;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /content-releases/:id/actions - Get all release actions
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user