mirror of
https://github.com/strapi/strapi.git
synced 2025-08-09 01:07:27 +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';
|
import releaseActionController from '../release-action';
|
||||||
|
|
||||||
const mockSanitizedQueryRead = jest.fn().mockResolvedValue({});
|
const mockSanitizedQueryRead = jest.fn().mockResolvedValue({});
|
||||||
const mockFindActions = jest.fn().mockResolvedValue({ results: [], pagination: {} });
|
const mockFindActions = jest.fn().mockResolvedValue({ results: [], pagination: {} });
|
||||||
const mockSanitizeOutput = jest.fn((entry: { id: number; name: string }) => ({ id: entry.id }));
|
const mockSanitizeOutput = jest.fn((entry: { id: number; name: string }) => ({ id: entry.id }));
|
||||||
|
const mockCreateAction = jest.fn();
|
||||||
|
|
||||||
jest.mock('../../utils', () => ({
|
jest.mock('../../utils', () => ({
|
||||||
getService: jest.fn(() => ({
|
getService: jest.fn(() => ({
|
||||||
@ -18,6 +20,7 @@ jest.mock('../../utils', () => ({
|
|||||||
displayName: 'contentTypeB',
|
displayName: 'contentTypeB',
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
createAction: mockCreateAction,
|
||||||
})),
|
})),
|
||||||
getPermissionsChecker: jest.fn(() => ({
|
getPermissionsChecker: jest.fn(() => ({
|
||||||
sanitizedQuery: {
|
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', () => {
|
describe('update', () => {
|
||||||
it('throws an error given bad request arguments', () => {
|
it('throws an error given bad request arguments', () => {
|
||||||
const ctx = {
|
const ctx = {
|
||||||
|
@ -7,12 +7,14 @@ import {
|
|||||||
} from './validation/release-action';
|
} from './validation/release-action';
|
||||||
import type {
|
import type {
|
||||||
CreateReleaseAction,
|
CreateReleaseAction,
|
||||||
|
CreateManyReleaseActions,
|
||||||
GetReleaseActions,
|
GetReleaseActions,
|
||||||
UpdateReleaseAction,
|
UpdateReleaseAction,
|
||||||
DeleteReleaseAction,
|
DeleteReleaseAction,
|
||||||
} from '../../../shared/contracts/release-actions';
|
} from '../../../shared/contracts/release-actions';
|
||||||
import { getService } from '../utils';
|
import { getService } from '../utils';
|
||||||
import { RELEASE_ACTION_MODEL_UID } from '../constants';
|
import { RELEASE_ACTION_MODEL_UID } from '../constants';
|
||||||
|
import { AlreadyOnReleaseError } from '../services/validation';
|
||||||
|
|
||||||
const releaseActionController = {
|
const releaseActionController = {
|
||||||
async create(ctx: Koa.Context) {
|
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) {
|
async findMany(ctx: Koa.Context) {
|
||||||
const releaseId: GetReleaseActions.Request['params']['releaseId'] = ctx.params.releaseId;
|
const releaseId: GetReleaseActions.Request['params']['releaseId'] = ctx.params.releaseId;
|
||||||
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
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',
|
method: 'GET',
|
||||||
path: '/:releaseId/actions',
|
path: '/:releaseId/actions',
|
||||||
|
@ -5,6 +5,13 @@ import type { Release, CreateRelease, UpdateRelease } from '../../../shared/cont
|
|||||||
import type { CreateReleaseAction } from '../../../shared/contracts/release-actions';
|
import type { CreateReleaseAction } from '../../../shared/contracts/release-actions';
|
||||||
import { RELEASE_MODEL_UID } from '../constants';
|
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 }) => ({
|
const createReleaseValidationService = ({ strapi }: { strapi: LoadedStrapi }) => ({
|
||||||
async validateUniqueEntry(
|
async validateUniqueEntry(
|
||||||
releaseId: CreateReleaseAction.Request['params']['releaseId'],
|
releaseId: CreateReleaseAction.Request['params']['releaseId'],
|
||||||
@ -29,7 +36,7 @@ const createReleaseValidationService = ({ strapi }: { strapi: LoadedStrapi }) =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (isEntryInRelease) {
|
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}`
|
`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
|
* GET /content-releases/:id/actions - Get all release actions
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user