Skip to content
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

Extract comments to a separate table #22295

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
0af9f89
Add directus_comments migration
licitdev Apr 23, 2024
e604ef8
Add comments controller and service
licitdev Apr 23, 2024
2b766a0
Remove from activity
licitdev Apr 23, 2024
42ad028
Update system-data and types
licitdev Apr 23, 2024
83fa430
Refactor app with new endpoints
licitdev Apr 23, 2024
e7f4127
Expose service
licitdev Apr 24, 2024
5aec07c
Update app minimal permissions
licitdev Apr 24, 2024
dbf60af
Add collection translation
licitdev Apr 24, 2024
34734b3
Define relations
licitdev Apr 24, 2024
99d990c
Allow comment creation only if there's item read access
licitdev Apr 24, 2024
f4c26d0
Patch for MSSQL double constraints issue
licitdev Apr 25, 2024
d9db780
Fix users service test
licitdev Apr 25, 2024
b41e109
Add sdk support
licitdev Apr 25, 2024
bc98219
Update specs
licitdev Apr 25, 2024
6be77a7
Fix formatting
licitdev Apr 25, 2024
a2ce34a
Fix specs error
licitdev Apr 25, 2024
ed9af99
Patch whoopsie
licitdev Apr 25, 2024
8bb327e
Remove obsolete GraphQL mutations
licitdev Apr 25, 2024
ee44a86
Update required fields
licitdev Apr 25, 2024
ccc0a29
Remove unused vars
licitdev Apr 25, 2024
62f6c5f
Allow edit and delete of legacy activity comments
licitdev Apr 26, 2024
c2ae97e
Remove legacy comments from SDK
licitdev Apr 26, 2024
d8c544e
Add changeset
licitdev Apr 26, 2024
d1c62d8
Merge branch 'main' into directus-comments
licitdev Apr 26, 2024
4a8fec8
Merge branch 'main' into directus-comments
licitdev Apr 30, 2024
c65b170
Batch upwards migration
licitdev Apr 30, 2024
c960674
Update SDK to use keysOrQuery
licitdev May 7, 2024
33629c1
Update implementation for keysOrQuery
licitdev May 7, 2024
e1053cb
Remove singleton check
licitdev May 7, 2024
40fe295
Update SDK to use keysOrQuery 2
licitdev May 7, 2024
8cde050
Update keysOrQuery typedoc
licitdev May 7, 2024
2c5a731
Fix import
licitdev May 7, 2024
fbcf1de
Merge branch 'main' into directus-comments
licitdev May 7, 2024
aec1c64
Update migration timestamp
licitdev May 9, 2024
a7aaf23
Merge branch 'main' into directus-comments
paescuj May 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/blue-pets-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---

Check warning on line 1 in .changeset/blue-pets-beam.md

View workflow job for this annotation

GitHub Actions / Lint

File ignored by default.
'@directus/api': major
'@directus/sdk': major
'@directus/system-data': minor
'@directus/specs': minor
'@directus/types': minor
'@directus/app': minor
---

Extracted comments to a separate table
2 changes: 2 additions & 0 deletions api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import activityRouter from './controllers/activity.js';
import assetsRouter from './controllers/assets.js';
import authRouter from './controllers/auth.js';
import collectionsRouter from './controllers/collections.js';
import commentsRouter from './controllers/comments.js';
import dashboardsRouter from './controllers/dashboards.js';
import extensionsRouter from './controllers/extensions.js';
import fieldsRouter from './controllers/fields.js';
Expand Down Expand Up @@ -280,6 +281,7 @@ export default async function createApp(): Promise<express.Application> {
app.use('/activity', activityRouter);
app.use('/assets', assetsRouter);
app.use('/collections', collectionsRouter);
app.use('/comments', commentsRouter);
app.use('/dashboards', dashboardsRouter);
app.use('/extensions', extensionsRouter);
app.use('/fields', fieldsRouter);
Expand Down
53 changes: 2 additions & 51 deletions api/src/controllers/activity.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { Action } from '@directus/constants';
import { isDirectusError } from '@directus/errors';
import { ErrorCode, ForbiddenError, InvalidPayloadError, isDirectusError } from '@directus/errors';
import express from 'express';
import Joi from 'joi';
import { ErrorCode, ForbiddenError, InvalidPayloadError } from '@directus/errors';
import { respond } from '../middleware/respond.js';
import useCollection from '../middleware/use-collection.js';
import { validateBatch } from '../middleware/validate-batch.js';
import { ActivityService } from '../services/activity.js';
import { MetaService } from '../services/meta.js';
import asyncHandler from '../utils/async-handler.js';
import { getIPFromReq } from '../utils/get-ip-from-req.js';

const router = express.Router();

Expand Down Expand Up @@ -68,54 +66,7 @@ router.get(
respond,
);

const createCommentSchema = Joi.object({
comment: Joi.string().required(),
collection: Joi.string().required(),
item: [Joi.number().required(), Joi.string().required()],
});

router.post(
'/comment',
asyncHandler(async (req, res, next) => {
const service = new ActivityService({
accountability: req.accountability,
schema: req.schema,
});

const { error } = createCommentSchema.validate(req.body);

if (error) {
throw new InvalidPayloadError({ reason: error.message });
}

const primaryKey = await service.createOne({
...req.body,
action: Action.COMMENT,
user: req.accountability?.user,
ip: getIPFromReq(req),
user_agent: req.accountability?.userAgent,
origin: req.get('origin'),
});

try {
const record = await service.readOne(primaryKey, req.sanitizedQuery);

res.locals['payload'] = {
data: record || null,
};
} catch (error: any) {
if (isDirectusError(error, ErrorCode.Forbidden)) {
return next();
}

throw error;
}

return next();
}),
respond,
);

// TODO: Remove legacy commenting in upcoming version
const updateCommentSchema = Joi.object({
comment: Joi.string().required(),
});
Expand Down
199 changes: 199 additions & 0 deletions api/src/controllers/comments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { ErrorCode, isDirectusError } from '@directus/errors';
import type { PrimaryKey } from '@directus/types';
import express from 'express';
import { respond } from '../middleware/respond.js';
import useCollection from '../middleware/use-collection.js';
import { validateBatch } from '../middleware/validate-batch.js';
import { CommentsService } from '../services/comments.js';
import { MetaService } from '../services/meta.js';
import asyncHandler from '../utils/async-handler.js';
import { sanitizeQuery } from '../utils/sanitize-query.js';

const router = express.Router();

router.use(useCollection('directus_comments'));

router.post(
'/',
asyncHandler(async (req, res, next) => {
const service = new CommentsService({
accountability: req.accountability,
schema: req.schema,
});

const savedKeys: PrimaryKey[] = [];

if (Array.isArray(req.body)) {
const keys = await service.createMany(req.body);
savedKeys.push(...keys);
} else {
const key = await service.createOne(req.body);
savedKeys.push(key);
}

try {
if (Array.isArray(req.body)) {
const records = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals['payload'] = { data: records };
} else {
const record = await service.readOne(savedKeys[0]!, req.sanitizedQuery);
res.locals['payload'] = { data: record };
}
} catch (error: any) {
if (isDirectusError(error, ErrorCode.Forbidden)) {
return next();
}

throw error;
}

return next();
}),
respond,
);

const readHandler = asyncHandler(async (req, res, next) => {
const service = new CommentsService({
accountability: req.accountability,
schema: req.schema,
});

const metaService = new MetaService({
accountability: req.accountability,
schema: req.schema,
});

let result;

if (req.body.keys) {
result = await service.readMany(req.body.keys, req.sanitizedQuery);
} else {
result = await service.readByQuery(req.sanitizedQuery);
}

const meta = await metaService.getMetaForQuery('directus_comments', req.sanitizedQuery);

res.locals['payload'] = { data: result, meta };
return next();
});

router.get('/', validateBatch('read'), readHandler, respond);
router.search('/', validateBatch('read'), readHandler, respond);

router.get(
'/:pk',
asyncHandler(async (req, res, next) => {
const service = new CommentsService({
accountability: req.accountability,
schema: req.schema,
});

const record = await service.readOne(req.params['pk']!, req.sanitizedQuery);

res.locals['payload'] = { data: record || null };
return next();
}),
respond,
);

router.patch(
'/',
validateBatch('update'),
asyncHandler(async (req, res, next) => {
const service = new CommentsService({
accountability: req.accountability,
schema: req.schema,
});

let keys: PrimaryKey[] = [];

if (Array.isArray(req.body)) {
keys = await service.updateBatch(req.body);
} else if (req.body.keys) {
keys = await service.updateMany(req.body.keys, req.body.data);
} else {
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
}
paescuj marked this conversation as resolved.
Show resolved Hide resolved

try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals['payload'] = { data: result };
} catch (error: any) {
if (isDirectusError(error, ErrorCode.Forbidden)) {
return next();
}

throw error;
}

return next();
}),
respond,
);

router.patch(
'/:pk',
asyncHandler(async (req, res, next) => {
const service = new CommentsService({
accountability: req.accountability,
schema: req.schema,
});

const primaryKey = await service.updateOne(req.params['pk']!, req.body);

try {
const record = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals['payload'] = { data: record };
} catch (error: any) {
if (isDirectusError(error, ErrorCode.Forbidden)) {
return next();
}

throw error;
}

return next();
}),
respond,
);

router.delete(
'/',
validateBatch('delete'),
asyncHandler(async (req, _res, next) => {
const service = new CommentsService({
accountability: req.accountability,
schema: req.schema,
});

if (Array.isArray(req.body)) {
await service.deleteMany(req.body);
} else if (req.body.keys) {
await service.deleteMany(req.body.keys);
} else {
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
await service.deleteByQuery(sanitizedQuery);
}

return next();
}),
respond,
);

router.delete(
'/:pk',
asyncHandler(async (req, _res, next) => {
const service = new CommentsService({
accountability: req.accountability,
schema: req.schema,
});

await service.deleteOne(req.params['pk']!);

return next();
}),
respond,
);

export default router;