feat: add filter and transaction for destroy action (#35)
* feat: add filter and transaction for destroy action * fix: batch destroy in to-many relactionship
This commit is contained in:
parent
0d3d30e0c2
commit
32a8483336
@ -9,6 +9,7 @@ describe('destroy', () => {
|
|||||||
|
|
||||||
afterAll(() => db.close());
|
afterAll(() => db.close());
|
||||||
|
|
||||||
|
describe('single', () => {
|
||||||
it('common1', async () => {
|
it('common1', async () => {
|
||||||
const Post = db.getModel('posts');
|
const Post = db.getModel('posts');
|
||||||
const post = await Post.create();
|
const post = await Post.create();
|
||||||
@ -18,7 +19,29 @@ describe('destroy', () => {
|
|||||||
expect(response.body).toBe(post.id);
|
expect(response.body).toBe(post.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hasOne1', async () => {
|
it('batch delete by filter', async () => {
|
||||||
|
const Post = db.getModel('posts');
|
||||||
|
const posts = await Post.bulkCreate([
|
||||||
|
{ title: 'title1', status: 'published'},
|
||||||
|
{ title: 'title2', status: 'draft'},
|
||||||
|
{ title: 'title3', status: 'published'},
|
||||||
|
{ title: 'title4', status: 'draft'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await agent
|
||||||
|
.delete('/posts?filter[status]=draft');
|
||||||
|
|
||||||
|
const published = await Post.findAll();
|
||||||
|
expect(published.length).toBe(2);
|
||||||
|
expect(published.map(({ title, status }) => ({ title, status }))).toEqual([
|
||||||
|
{ title: 'title1', status: 'published'},
|
||||||
|
{ title: 'title3', status: 'published'}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasOne', () => {
|
||||||
|
it('delete has-one item', async () => {
|
||||||
const User = db.getModel('users');
|
const User = db.getModel('users');
|
||||||
const user = await User.create();
|
const user = await User.create();
|
||||||
await user.updateAssociations({
|
await user.updateAssociations({
|
||||||
@ -31,8 +54,10 @@ describe('destroy', () => {
|
|||||||
const profile = await user.getProfile();
|
const profile = await user.getProfile();
|
||||||
expect(profile).toBeNull();
|
expect(profile).toBeNull();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('hasMany1', async () => {
|
describe('hasMany', () => {
|
||||||
|
it('delete single item in has-many list', async () => {
|
||||||
const Post = db.getModel('posts');
|
const Post = db.getModel('posts');
|
||||||
const post = await Post.create();
|
const post = await Post.create();
|
||||||
await post.updateAssociations({
|
await post.updateAssociations({
|
||||||
@ -40,14 +65,34 @@ describe('destroy', () => {
|
|||||||
{content: 'content111222'},
|
{content: 'content111222'},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
let [comment] = await post.getComments();
|
const [comment] = await post.getComments();
|
||||||
await agent
|
await agent
|
||||||
.delete(`/posts/${post.id}/comments/${comment.id}`);
|
.delete(`/posts/${post.id}/comments/${comment.id}`);
|
||||||
const count = await post.countComments();
|
const count = await post.countComments();
|
||||||
expect(count).toBe(0);
|
expect(count).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('belongsTo1', async () => {
|
it('delete batch items in has-many list', async () => {
|
||||||
|
const Post = db.getModel('posts');
|
||||||
|
const post = await Post.create();
|
||||||
|
await post.updateAssociations({
|
||||||
|
comments: [
|
||||||
|
{ content: 'content1', status: 'published' },
|
||||||
|
{ content: 'content2', status: 'draft'},
|
||||||
|
{ content: 'content3', status: 'published' },
|
||||||
|
{ content: 'content4', status: 'draft'},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await agent
|
||||||
|
.delete(`/posts/${post.id}/comments?filter[status]=draft`);
|
||||||
|
const comments = await post.getComments();
|
||||||
|
expect(comments.length).toBe(2);
|
||||||
|
expect(comments.map(({ content }) => content)).toEqual(['content1', 'content3']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('belongsTo', () => {
|
||||||
|
it('delete belongs-to item', async () => {
|
||||||
const Post = db.getModel('posts');
|
const Post = db.getModel('posts');
|
||||||
const post = await Post.create();
|
const post = await Post.create();
|
||||||
await post.updateAssociations({
|
await post.updateAssociations({
|
||||||
@ -57,8 +102,10 @@ describe('destroy', () => {
|
|||||||
const user = await post.getUser();
|
const user = await post.getUser();
|
||||||
expect(user).toBeNull();
|
expect(user).toBeNull();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('belongsToMany', async () => {
|
describe('belongsToMany', () => {
|
||||||
|
it('delete single target item', async () => {
|
||||||
const Post = db.getModel('posts');
|
const Post = db.getModel('posts');
|
||||||
const post = await Post.create();
|
const post = await Post.create();
|
||||||
await post.updateAssociations({
|
await post.updateAssociations({
|
||||||
@ -71,5 +118,31 @@ describe('destroy', () => {
|
|||||||
.delete(`/posts/${post.id}/tags:destroy/${tag.id}`);
|
.delete(`/posts/${post.id}/tags:destroy/${tag.id}`);
|
||||||
const tags = await post.getTags();
|
const tags = await post.getTags();
|
||||||
expect(tags.length).toBe(0);
|
expect(tags.length).toBe(0);
|
||||||
|
|
||||||
|
const PostsTags = db.getModel('posts_tags');
|
||||||
|
const postsTags = await PostsTags.findAll();
|
||||||
|
expect(postsTags.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('delete batch target item by filter', async () => {
|
||||||
|
const Post = db.getModel('posts');
|
||||||
|
const post = await Post.create();
|
||||||
|
await post.updateAssociations({
|
||||||
|
tags: [
|
||||||
|
{ name: 'tag1', status: 'enabled'},
|
||||||
|
{ name: 'tag2', status: 'disabled' },
|
||||||
|
{ name: 'tag3', status: 'enabled'},
|
||||||
|
{ name: 'tag4', status: 'disabled' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await agent
|
||||||
|
.delete(`/posts/${post.id}/tags:destroy?filter[status]=disabled`);
|
||||||
|
const tags = await post.getTags();
|
||||||
|
expect(tags.length).toBe(2);
|
||||||
|
|
||||||
|
const PostsTags = db.getModel('posts_tags');
|
||||||
|
const postsTags = await PostsTags.findAll();
|
||||||
|
expect(postsTags.length).toBe(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -144,19 +144,17 @@ export async function get(ctx: Context, next: Next) {
|
|||||||
associated,
|
associated,
|
||||||
resourceField,
|
resourceField,
|
||||||
associatedName,
|
associatedName,
|
||||||
} = ctx.action.params as {
|
resourceName,
|
||||||
associated: Model,
|
resourceKey,
|
||||||
associatedName: string,
|
resourceKeyAttribute,
|
||||||
resourceField: Relation,
|
fields = []
|
||||||
values: any,
|
} = ctx.action.params;
|
||||||
};
|
|
||||||
if (associated && resourceField) {
|
if (associated && resourceField) {
|
||||||
const AssociatedModel = ctx.db.getModel(associatedName);
|
const AssociatedModel = ctx.db.getModel(associatedName);
|
||||||
if (!(associated instanceof AssociatedModel)) {
|
if (!(associated instanceof AssociatedModel)) {
|
||||||
throw new Error(`${associatedName} associated model invalid`);
|
throw new Error(`${associatedName} associated model invalid`);
|
||||||
}
|
}
|
||||||
const getAccessor = resourceField.getAccessors().get;
|
const getAccessor = resourceField.getAccessors().get;
|
||||||
const { resourceKey, resourceKeyAttribute, fields = [] } = ctx.action.params;
|
|
||||||
const TargetModel = ctx.db.getModel(resourceField.getTarget());
|
const TargetModel = ctx.db.getModel(resourceField.getTarget());
|
||||||
const options = TargetModel.parseApiJson({
|
const options = TargetModel.parseApiJson({
|
||||||
fields,
|
fields,
|
||||||
@ -175,7 +173,6 @@ export async function get(ctx: Context, next: Next) {
|
|||||||
ctx.body = model;
|
ctx.body = model;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const { resourceName, resourceKey, resourceKeyAttribute, fields = [] } = ctx.action.params;
|
|
||||||
const Model = ctx.db.getModel(resourceName);
|
const Model = ctx.db.getModel(resourceName);
|
||||||
const options = Model.parseApiJson({
|
const options = Model.parseApiJson({
|
||||||
fields,
|
fields,
|
||||||
@ -302,53 +299,52 @@ export async function destroy(ctx: Context, next: Next) {
|
|||||||
associated,
|
associated,
|
||||||
resourceField,
|
resourceField,
|
||||||
associatedName,
|
associatedName,
|
||||||
} = ctx.action.params as {
|
resourceName,
|
||||||
associated: Model,
|
resourceKey,
|
||||||
associatedName: string,
|
resourceKeyAttribute,
|
||||||
resourceField: Relation,
|
filter
|
||||||
values: any,
|
} = ctx.action.params;
|
||||||
};
|
const transaction = await ctx.db.sequelize.transaction();
|
||||||
|
const commonOptions = { transaction, context: ctx };
|
||||||
if (associated && resourceField) {
|
if (associated && resourceField) {
|
||||||
const AssociatedModel = ctx.db.getModel(associatedName);
|
const AssociatedModel = ctx.db.getModel(associatedName);
|
||||||
if (!(associated instanceof AssociatedModel)) {
|
if (!(associated instanceof AssociatedModel)) {
|
||||||
|
await transaction.rollback();
|
||||||
throw new Error(`${associatedName} associated model invalid`);
|
throw new Error(`${associatedName} associated model invalid`);
|
||||||
}
|
}
|
||||||
const {get: getAccessor, remove: removeAccessor, set: setAccessor} = resourceField.getAccessors();
|
const {get: getAccessor, remove: removeAccessor, set: setAccessor} = resourceField.getAccessors();
|
||||||
const { resourceKey, resourceKeyAttribute, fields = [] } = ctx.action.params;
|
|
||||||
const TargetModel = ctx.db.getModel(resourceField.getTarget());
|
const TargetModel = ctx.db.getModel(resourceField.getTarget());
|
||||||
const options = TargetModel.parseApiJson({
|
const { where } = TargetModel.parseApiJson({ filter, context: ctx });
|
||||||
fields,
|
|
||||||
});
|
|
||||||
if (resourceField instanceof HasOne || resourceField instanceof BelongsTo) {
|
if (resourceField instanceof HasOne || resourceField instanceof BelongsTo) {
|
||||||
const model: Model = await associated[getAccessor]({ ...options, context: ctx });
|
const model: Model = await associated[getAccessor](commonOptions);
|
||||||
await associated[setAccessor](null);
|
await associated[setAccessor](null, commonOptions);
|
||||||
ctx.body = await model.destroy();
|
// @ts-ignore
|
||||||
|
ctx.body = await model.destroy(commonOptions);
|
||||||
} else if (resourceField instanceof HasMany || resourceField instanceof BelongsToMany) {
|
} else if (resourceField instanceof HasMany || resourceField instanceof BelongsToMany) {
|
||||||
const [model]: Model[] = await associated[getAccessor]({
|
const primaryKey = resourceKeyAttribute || resourceField.options.targetKey || TargetModel.primaryKeyAttribute;
|
||||||
...options,
|
const models: Model[] = await associated[getAccessor]({
|
||||||
where: {
|
where: resourceKey ? { [primaryKey]: resourceKey } : where,
|
||||||
[resourceKeyAttribute || resourceField.options.targetKey || TargetModel.primaryKeyAttribute]: resourceKey,
|
...commonOptions
|
||||||
},
|
});
|
||||||
context: ctx,
|
await associated[removeAccessor](models, commonOptions);
|
||||||
|
// @ts-ignore
|
||||||
|
ctx.body = await TargetModel.destroy({
|
||||||
|
where: { [primaryKey]: { [Op.in]: models.map(item => item[primaryKey]) } },
|
||||||
|
...commonOptions
|
||||||
});
|
});
|
||||||
await associated[removeAccessor](model);
|
|
||||||
ctx.body = await model.destroy();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const { resourceName, resourceKey, resourceKeyAttribute, values } = ctx.action.params;
|
|
||||||
const Model = ctx.db.getModel(resourceName);
|
const Model = ctx.db.getModel(resourceName);
|
||||||
const resourceKeys = resourceKey ? resourceKey.split(',') : values[`${resourceKeyAttribute || Model.primaryKeyAttribute}s`];
|
const { where } = Model.parseApiJson({ filter, context: ctx });
|
||||||
|
const primaryKey = resourceKeyAttribute || Model.primaryKeyAttribute;
|
||||||
const data = await Model.destroy({
|
const data = await Model.destroy({
|
||||||
where: {
|
where: resourceKey ? { [primaryKey]: resourceKey } : where,
|
||||||
[resourceKeyAttribute || Model.primaryKeyAttribute]: {
|
|
||||||
[Op.in]: resourceKeys,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// @ts-ignore hooks 里添加 context
|
// @ts-ignore hooks 里添加 context
|
||||||
context: ctx,
|
...commonOptions,
|
||||||
});
|
});
|
||||||
ctx.body = data;
|
ctx.body = data;
|
||||||
}
|
}
|
||||||
|
await transaction.commit();
|
||||||
await next();
|
await next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,7 @@ export function parseRequest(request: ParseRequest, options: ParseOptions = {}):
|
|||||||
'/:resourceName': {
|
'/:resourceName': {
|
||||||
get: accessors.list,
|
get: accessors.list,
|
||||||
post: accessors.create,
|
post: accessors.create,
|
||||||
|
delete: accessors.delete
|
||||||
},
|
},
|
||||||
'/:resourceName/:resourceKey': {
|
'/:resourceName/:resourceKey': {
|
||||||
get: accessors.get,
|
get: accessors.get,
|
||||||
@ -84,6 +85,7 @@ export function parseRequest(request: ParseRequest, options: ParseOptions = {}):
|
|||||||
'/:associatedName/:associatedKey/:resourceName': {
|
'/:associatedName/:associatedKey/:resourceName': {
|
||||||
get: accessors.list,
|
get: accessors.list,
|
||||||
post: accessors.create,
|
post: accessors.create,
|
||||||
|
delete: accessors.delete,
|
||||||
},
|
},
|
||||||
'/:associatedName/:associatedKey/:resourceName/:resourceKey': {
|
'/:associatedName/:associatedKey/:resourceName/:resourceKey': {
|
||||||
get: accessors.get,
|
get: accessors.get,
|
||||||
@ -106,6 +108,7 @@ export function parseRequest(request: ParseRequest, options: ParseOptions = {}):
|
|||||||
'/:associatedName/:associatedKey/:resourceName': {
|
'/:associatedName/:associatedKey/:resourceName': {
|
||||||
get: accessors.list,
|
get: accessors.list,
|
||||||
post: accessors.create,
|
post: accessors.create,
|
||||||
|
delete: accessors.delete,
|
||||||
},
|
},
|
||||||
'/:associatedName/:associatedKey/:resourceName/:resourceKey': {
|
'/:associatedName/:associatedKey/:resourceName/:resourceKey': {
|
||||||
get: accessors.get,
|
get: accessors.get,
|
||||||
|
Loading…
Reference in New Issue
Block a user