1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-24 07:39:44 +02:00

feat: Version 2

Closes #627, closes #1047
This commit is contained in:
Maksim Eltyshev 2025-05-10 02:09:06 +02:00
parent ad7fb51cfa
commit 2ee1166747
1557 changed files with 76832 additions and 47042 deletions

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
/**
* current-user hook
*
@ -17,10 +22,7 @@ module.exports = function defineCurrentUserHook(sails) {
return null;
}
const session = await Session.findOne({
accessToken,
deletedAt: null,
});
const session = await Session.qm.getOneUndeletedByAccessToken(accessToken);
if (!session) {
return null;
@ -30,9 +32,15 @@ module.exports = function defineCurrentUserHook(sails) {
return null;
}
const user = await sails.helpers.users.getOne(payload.subject);
const user = await User.qm.getOneById(payload.subject, {
withDeactivated: false,
});
if (user && user.passwordChangedAt > payload.issuedAt) {
if (!user) {
return null;
}
if (user.passwordChangedAt > payload.issuedAt) {
return null;
}
@ -66,6 +74,10 @@ module.exports = function defineCurrentUserHook(sails) {
if (sessionAndUser) {
const { session, user } = sessionAndUser;
if (user.language) {
req.setLocale(user.language);
}
Object.assign(req, {
currentSession: session,
currentUser: user,

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const fs = require('fs');
const fse = require('fs-extra');
const path = require('path');
@ -27,21 +32,80 @@ class LocalFileManager {
}
// eslint-disable-next-line class-methods-use-this
read(filePathSegment) {
async read(filePathSegment) {
const filePath = buildPath(filePathSegment);
const isFileExists = await fse.pathExists(filePath);
if (!fs.existsSync(filePath)) {
if (!isFileExists) {
throw new Error('File does not exist');
}
return fs.createReadStream(filePath);
}
// eslint-disable-next-line class-methods-use-this
async getSizeInBytes(filePathSegment) {
let result;
try {
result = await fs.promises.stat(buildPath(filePathSegment));
} catch (error) {
return null;
}
return result.size;
}
// eslint-disable-next-line class-methods-use-this
async rename(filePathSegment, nextFilePathSegment) {
try {
await fs.promises.rename(buildPath(filePathSegment), buildPath(nextFilePathSegment));
} catch (error) {
/* empty */
}
}
// eslint-disable-next-line class-methods-use-this
async delete(filePathSegment) {
try {
await fs.promises.unlink(buildPath(filePathSegment));
} catch (error) {
/* empty */
}
}
// eslint-disable-next-line class-methods-use-this
async listDir(dirPathSegment) {
let dirents;
try {
dirents = await fs.promises.readdir(buildPath(dirPathSegment), {
withFileTypes: true,
});
} catch (error) {
return null;
}
return dirents.flatMap((dirent) => (dirent.isDirectory() ? dirent.name : []));
}
// eslint-disable-next-line class-methods-use-this
async renameDir(dirPathSegment, nextDirPathSegment) {
try {
await fs.promises.rename(buildPath(dirPathSegment), buildPath(nextDirPathSegment));
} catch (error) {
/* empty */
}
}
// eslint-disable-next-line class-methods-use-this
async deleteDir(dirPathSegment) {
await rimraf(buildPath(dirPathSegment));
}
// eslint-disable-next-line class-methods-use-this
async isExists(pathSegment) {
return fse.pathExists(buildPath(pathSegment));
}
// eslint-disable-next-line class-methods-use-this
buildUrl(filePathSegment) {
return `${sails.config.custom.baseUrl}/${filePathSegment.replace(PATH_SEGMENT_TO_URL_REPLACE_REGEX, '')}`;

View file

@ -1,7 +1,15 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const fs = require('fs');
const {
CopyObjectCommand,
DeleteObjectsCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
} = require('@aws-sdk/client-s3');
@ -20,7 +28,6 @@ class S3FileManager {
});
await this.client.send(command);
return null;
}
@ -45,26 +52,144 @@ class S3FileManager {
return result.Body;
}
async deleteDir(dirPathSegment) {
async getSizeInBytes(filePathSegment) {
const headObjectCommand = new HeadObjectCommand({
Bucket: sails.config.custom.s3Bucket,
Key: filePathSegment,
});
let result;
try {
result = await this.client.send(headObjectCommand);
} catch (error) {
return null;
}
return result.ContentLength;
}
async rename(filePathSegment, nextFilePathSegment) {
const copyObjectCommand = new CopyObjectCommand({
Bucket: sails.config.custom.s3Bucket,
Key: nextFilePathSegment,
CopySource: `${sails.config.custom.s3Bucket}/${filePathSegment}`,
});
try {
await this.client.send(copyObjectCommand);
} catch (error) {
return;
}
await this.delete(filePathSegment);
}
async delete(filePathSegment) {
const deleteObjectCommand = new DeleteObjectCommand({
Bucket: sails.config.custom.s3Bucket,
Key: filePathSegment,
});
await this.client.send(deleteObjectCommand);
}
async listDir(dirPathSegment, { ContinuationToken } = {}) {
const listObjectsCommand = new ListObjectsV2Command({
ContinuationToken,
Bucket: sails.config.custom.s3Bucket,
Prefix: `${dirPathSegment}/`,
Delimiter: '/',
});
const result = await this.client.send(listObjectsCommand);
if (!result.CommonPrefixes) {
return null;
}
const dirnames = result.CommonPrefixes.map(({ Prefix }) =>
Prefix.slice(dirPathSegment.length + 1, -1),
);
if (result.IsTruncated) {
const otherDirnames = await this.listDir(dirPathSegment, {
ContinuationToken: result.NextContinuationToken,
});
if (otherDirnames) {
dirnames.push(...otherDirnames);
}
}
return dirnames;
}
async renameDir(dirPathSegment, nextDirPathSegment, { ContinuationToken } = {}) {
const listObjectsCommand = new ListObjectsV2Command({
ContinuationToken,
Bucket: sails.config.custom.s3Bucket,
Prefix: dirPathSegment,
});
const result = await this.client.send(listObjectsCommand);
if (!result.Contents || result.Contents.length === 0) {
if (!result.Contents) {
return;
}
const deleteObjectsCommand = new DeleteObjectsCommand({
// eslint-disable-next-line no-restricted-syntax
for (const { Key } of result.Contents) {
// eslint-disable-next-line no-await-in-loop
await this.rename(Key, `${nextDirPathSegment}/${Key.substring(dirPathSegment.length + 1)}`);
}
if (result.IsTruncated) {
await this.renameDir(dirPathSegment, nextDirPathSegment, {
ContinuationToken: result.NextContinuationToken,
});
}
}
async deleteDir(dirPathSegment, { ContinuationToken } = {}) {
const listObjectsCommand = new ListObjectsV2Command({
ContinuationToken,
Bucket: sails.config.custom.s3Bucket,
Delete: {
Objects: result.Contents.map(({ Key }) => ({ Key })),
},
Prefix: dirPathSegment,
});
await this.client.send(deleteObjectsCommand);
const result = await this.client.send(listObjectsCommand);
if (!result.Contents) {
return;
}
if (result.Contents.length > 0) {
const deleteObjectsCommand = new DeleteObjectsCommand({
Bucket: sails.config.custom.s3Bucket,
Delete: {
Objects: result.Contents.map(({ Key }) => ({ Key })),
},
});
await this.client.send(deleteObjectsCommand);
}
if (result.IsTruncated) {
await this.deleteDir(dirPathSegment, {
ContinuationToken: result.NextContinuationToken,
});
}
}
async isExists(pathSegment) {
const listObjectsCommand = new ListObjectsV2Command({
Bucket: sails.config.custom.s3Bucket,
Prefix: pathSegment,
MaxKeys: 1,
});
const result = await this.client.send(listObjectsCommand);
return !!result.Contents && result.Contents.length === 1;
}
// eslint-disable-next-line class-methods-use-this

View file

@ -1,5 +1,7 @@
const LocalFileManager = require('./LocalFileManager');
const S3FileManager = require('./S3FileManager');
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
/**
* file-manager hook
@ -9,11 +11,14 @@ const S3FileManager = require('./S3FileManager');
* @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
*/
const LocalFileManager = require('./LocalFileManager');
const S3FileManager = require('./S3FileManager');
module.exports = function defineFileManagerHook(sails) {
let instance = null;
const createInstance = () => {
instance = sails.hooks.s3.isActive()
instance = sails.hooks.s3.isEnabled()
? new S3FileManager(sails.hooks.s3.getClient())
: new LocalFileManager();
};

View file

@ -1,4 +1,7 @@
const openidClient = require('openid-client');
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
/**
* oidc hook
@ -8,6 +11,8 @@ const openidClient = require('openid-client');
* @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
*/
const openidClient = require('openid-client');
module.exports = function defineOidcHook(sails) {
let client = null;
@ -16,18 +21,25 @@ module.exports = function defineOidcHook(sails) {
* Runs when this Sails app loads/lifts.
*/
async initialize() {
if (!this.isActive()) {
if (!this.isEnabled()) {
return;
}
sails.log.info('Initializing custom hook (`oidc`)');
},
// TODO: wait for initialization if called more than once
async getClient() {
if (client === null && this.isActive()) {
if (this.isEnabled() && !this.isActive()) {
sails.log.info('Initializing OIDC client');
const issuer = await openidClient.Issuer.discover(sails.config.custom.oidcIssuer);
let issuer;
try {
issuer = await openidClient.Issuer.discover(sails.config.custom.oidcIssuer);
} catch (error) {
sails.log.warn(`Error while initializing OIDC client: ${error}`);
return null;
}
const metadata = {
client_id: sails.config.custom.oidcClientId,
@ -47,8 +59,12 @@ module.exports = function defineOidcHook(sails) {
return client;
},
isEnabled() {
return !!sails.config.custom.oidcIssuer;
},
isActive() {
return sails.config.custom.oidcIssuer !== undefined;
return client !== null;
},
};
};

View file

@ -0,0 +1,55 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
/**
* query-methods hook
*
* @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,
* and/or initialization logic.
* @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
*/
const fs = require('fs');
const path = require('path');
module.exports = function defineQueryMethodsHook(sails) {
const addQueryMethods = () => {
const queryMethodsByModelName = fs.readdirSync(path.join(__dirname, 'models')).reduce(
(result, filename) => ({
...result,
// eslint-disable-next-line global-require, import/no-dynamic-require
[path.parse(filename).name]: require(path.join(__dirname, 'models', filename)),
}),
{},
);
_(sails.models).forEach((Model) => {
const queryMethods = queryMethodsByModelName[Model.globalId];
if (queryMethods) {
Object.assign(Model, {
qm: queryMethods,
});
}
});
};
return {
/**
* Runs when this Sails app loads/lifts.
*/
async initialize() {
sails.log.info('Initializing custom hook (`query-methods`)');
return new Promise((resolve) => {
sails.after('hook:orm:loaded', () => {
addQueryMethods();
resolve();
});
});
},
};
};

View file

@ -0,0 +1,39 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const LIMIT = 50;
/* Query methods */
const create = (arrayOfValues) => Action.createEach(arrayOfValues).fetch();
const createOne = (values) => Action.create({ ...values }).fetch();
const getByCardId = (cardId, { beforeId } = {}) => {
const criteria = {
cardId,
};
if (beforeId) {
criteria.id = {
'<': beforeId,
};
}
return Action.find(criteria).sort('id DESC').limit(LIMIT);
};
const update = (criteria, values) => Action.update(criteria).set(values).fetch();
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => Action.destroy(criteria).fetch();
module.exports = {
create,
createOne,
getByCardId,
update,
delete: delete_,
};

View file

@ -0,0 +1,231 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
// TODO: refactor?
const defaultFind = (criteria) => Attachment.find(criteria).sort('id');
/* Query methods */
const create = (arrayOfValues) => {
const arrayOfFileValues = arrayOfValues.filter(({ type }) => type === Attachment.Types.FILE);
if (arrayOfFileValues.length > 0) {
const arrayOfValuesByFileReferenceId = _.groupBy(arrayOfFileValues, 'data.fileReferenceId');
const fileReferenceIds = Object.keys(arrayOfValuesByFileReferenceId);
const fileReferenceIdsByTotal = Object.entries(arrayOfValuesByFileReferenceId).reduce(
(result, [fileReferenceId, arrayOfValuesItem]) => ({
...result,
[arrayOfValuesItem.length]: [...(result[arrayOfValuesItem.length] || []), fileReferenceId],
}),
{},
);
return sails.getDatastore().transaction(async (db) => {
const queryValues = [];
let query = `UPDATE file_reference SET total = total + CASE `;
Object.entries(fileReferenceIdsByTotal).forEach(([total, fileReferenceIdsItem]) => {
const inValues = fileReferenceIdsItem.map((fileReferenceId) => {
queryValues.push(fileReferenceId);
return `$${queryValues.length}`;
});
queryValues.push(total);
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
});
const inValues = fileReferenceIds.map((fileReferenceId) => {
queryValues.push(fileReferenceId);
return `$${queryValues.length}`;
});
queryValues.push(new Date().toISOString());
query += `END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND total IS NOT NULL RETURNING id`;
const queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);
const nextFileReferenceIds = sails.helpers.utils.mapRecords(queryResult.rows);
if (nextFileReferenceIds.length < fileReferenceIds.length) {
const nextFileReferenceIdsSet = new Set(nextFileReferenceIds);
// eslint-disable-next-line no-param-reassign
arrayOfValues = arrayOfValues.filter(
(values) =>
values.type !== Attachment.Types.FILE ||
nextFileReferenceIdsSet.has(values.data.fileReferenceId),
);
}
return Attachment.createEach(arrayOfValues).fetch().usingConnection(db);
});
}
return Attachment.createEach(arrayOfValues).fetch();
};
const createOne = (values) => {
if (values.type === Attachment.Types.FILE) {
const { fileReferenceId } = values.data;
return sails.getDatastore().transaction(async (db) => {
const attachment = await Attachment.create({ ...values })
.fetch()
.usingConnection(db);
const queryResult = await sails
.sendNativeQuery(
'UPDATE file_reference SET total = total + 1, updated_at = $1 WHERE id = $2 AND total IS NOT NULL',
[new Date().toISOString(), fileReferenceId],
)
.usingConnection(db);
if (queryResult.rowCount === 0) {
throw 'fileReferenceNotFound';
}
return attachment;
});
}
return Attachment.create({ ...values }).fetch();
};
const getByIds = (ids) => defaultFind(ids);
const getByCardId = (cardId) =>
defaultFind({
cardId,
});
const getByCardIds = (cardIds) =>
defaultFind({
cardId: cardIds,
});
const getOneById = (id, { cardId } = {}) => {
const criteria = {
id,
};
if (cardId) {
criteria.cardId = cardId;
}
return Attachment.findOne(criteria);
};
const update = (criteria, values) => Attachment.update(criteria).set(values).fetch();
const updateOne = (criteria, values) => Attachment.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) =>
sails.getDatastore().transaction(async (db) => {
const attachments = await Attachment.destroy(criteria).fetch().usingConnection(db);
const fileAttachments = attachments.filter(({ type }) => type === Attachment.Types.FILE);
let fileReferences = [];
if (fileAttachments.length > 0) {
const attachmentsByFileReferenceId = _.groupBy(fileAttachments, 'data.fileReferenceId');
const fileReferenceIdsByTotal = Object.entries(attachmentsByFileReferenceId).reduce(
(result, [fileReferenceId, attachmentsItem]) => ({
...result,
[attachmentsItem.length]: [...(result[attachmentsItem.length] || []), fileReferenceId],
}),
{},
);
const queryValues = [];
let query = 'UPDATE file_reference SET total = CASE WHEN total = CASE ';
Object.entries(fileReferenceIdsByTotal).forEach(([total, fileReferenceIds]) => {
const inValues = fileReferenceIds.map((fileReferenceId) => {
queryValues.push(fileReferenceId);
return `$${queryValues.length}`;
});
queryValues.push(total);
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
});
query += 'END THEN NULL ELSE total - CASE ';
Object.entries(fileReferenceIdsByTotal).forEach(([total, fileReferenceIds]) => {
const inValues = fileReferenceIds.map((fileReferenceId) => {
queryValues.push(fileReferenceId);
return `$${queryValues.length}`;
});
queryValues.push(total);
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
});
const inValues = Object.keys(attachmentsByFileReferenceId).map((fileReferenceId) => {
queryValues.push(fileReferenceId);
return `$${queryValues.length}`;
});
queryValues.push(new Date().toISOString());
query += `END END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND total IS NOT NULL RETURNING id, total`;
const queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);
fileReferences = queryResult.rows;
}
return {
attachments,
fileReferences,
};
});
const deleteOne = async (criteria, { isFile } = {}) => {
let fileReference = null;
if (isFile) {
return sails.getDatastore().transaction(async (db) => {
const attachment = await Attachment.destroyOne(criteria).usingConnection(db);
if (attachment.type === Attachment.Types.FILE) {
const queryResult = await sails
.sendNativeQuery(
'UPDATE file_reference SET total = CASE WHEN total > 1 THEN total - 1 END, updated_at = $1 WHERE id = $2 RETURNING id, total',
[new Date().toISOString(), attachment.data.fileReferenceId],
)
.usingConnection(db);
[fileReference] = queryResult.rows;
}
return {
attachment,
fileReference,
};
});
}
const attachment = await Attachment.destroyOne(criteria);
return {
attachment,
fileReference,
};
};
module.exports = {
create,
createOne,
getByIds,
getByCardId,
getByCardIds,
getOneById,
update,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,49 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => BackgroundImage.find(criteria).sort('id');
/* Query methods */
const createOne = (values) => BackgroundImage.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByProjectId = (projectId) =>
defaultFind({
projectId,
});
const getByProjectIds = (projectIds) =>
defaultFind({
projectId: projectIds,
});
const getOneById = (id, { projectId } = {}) => {
const criteria = {
id,
};
if (projectId) {
criteria.projectId = projectId;
}
return BackgroundImage.findOne(criteria);
};
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => BackgroundImage.destroy(criteria).fetch();
const deleteOne = (criteria) => BackgroundImage.destroyOne(criteria);
module.exports = {
createOne,
getByIds,
getByProjectId,
getByProjectIds,
getOneById,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,52 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => BaseCustomFieldGroup.find(criteria).sort('id');
/* Query methods */
const createOne = (values) => BaseCustomFieldGroup.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByProjectId = (projectId) =>
defaultFind({
projectId,
});
const getByProjectIds = (projectIds) =>
defaultFind({
projectId: projectIds,
});
const getOneById = (id, { projectId } = {}) => {
const criteria = {
id,
};
if (projectId) {
criteria.projectId = projectId;
}
return BaseCustomFieldGroup.findOne(criteria);
};
const updateOne = (criteria, values) => BaseCustomFieldGroup.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => BaseCustomFieldGroup.destroy(criteria).fetch();
const deleteOne = (criteria) => BaseCustomFieldGroup.destroyOne(criteria);
module.exports = {
createOne,
getByIds,
getByProjectId,
getByProjectIds,
getOneById,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,91 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria, { sort = 'id' } = {}) => Board.find(criteria).sort(sort);
/* Query methods */
const createOne = (values, { user } = {}) =>
sails.getDatastore().transaction(async (db) => {
const board = await Board.create({ ...values })
.fetch()
.usingConnection(db);
const boardMembership = await BoardMembership.create({
projectId: board.projectId,
boardId: board.id,
userId: user.id,
role: BoardMembership.Roles.EDITOR,
})
.fetch()
.usingConnection(db);
const lists = await List.createEach(
[List.Types.ARCHIVE, List.Types.TRASH].map((type) => ({
type,
boardId: board.id,
})),
)
.fetch()
.usingConnection(db);
return { board, boardMembership, lists };
});
const getByIds = (ids, { exceptProjectIdOrIds } = {}) => {
const criteria = {
id: ids,
};
if (exceptProjectIdOrIds) {
criteria.projectId = {
'!=': exceptProjectIdOrIds,
};
}
return defaultFind(criteria);
};
const getByProjectId = (projectId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {
const criteria = {
projectId,
};
if (exceptIdOrIds) {
criteria.id = {
'!=': exceptIdOrIds,
};
}
return defaultFind(criteria, { sort });
};
const getByProjectIds = (projectIds, { sort = ['position', 'id'] } = {}) =>
defaultFind(
{
projectId: projectIds,
},
{ sort },
);
const getOneById = (id) => Board.findOne(id);
const updateOne = (criteria, values) => Board.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => Board.destroy(criteria).fetch();
const deleteOne = (criteria) => Board.destroyOne(criteria);
module.exports = {
createOne,
getByIds,
getByProjectId,
getByProjectIds,
getOneById,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,100 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => BoardMembership.find(criteria).sort('id');
/* Query methods */
const createOne = (values) => BoardMembership.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByProjectId = (projectId) =>
defaultFind({
projectId,
});
const getByProjectIdAndUserId = (projectId, userId) =>
defaultFind({
projectId,
userId,
});
const getByProjectIds = (projectIds) =>
defaultFind({
projectId: projectIds,
});
const getByBoardId = (boardId) =>
defaultFind({
boardId,
});
const getByBoardIds = (boardIds, { exceptUserIdOrIds } = {}) => {
const criteria = {
boardId: boardIds,
};
if (exceptUserIdOrIds) {
criteria.userId = {
'!=': exceptUserIdOrIds,
};
}
return defaultFind(criteria);
};
const getByBoardIdsAndUserId = (boardIds, userId) =>
defaultFind({
userId,
boardId: boardIds,
});
const getByUserId = (userId, { exceptProjectIdOrIds } = {}) => {
const criteria = {
userId,
};
if (exceptProjectIdOrIds) {
criteria.projectId = {
'!=': exceptProjectIdOrIds,
};
}
return defaultFind(criteria);
};
const getOneById = (id) => BoardMembership.findOne(id);
const getOneByBoardIdAndUserId = (boardId, userId) =>
BoardMembership.findOne({
boardId,
userId,
});
const updateOne = async (criteria, values) =>
BoardMembership.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => BoardMembership.destroy(criteria).fetch();
const deleteOne = (criteria) => BoardMembership.destroyOne(criteria);
module.exports = {
createOne,
getByIds,
getByProjectId,
getByProjectIdAndUserId,
getByProjectIds,
getByBoardId,
getByBoardIds,
getByBoardIdsAndUserId,
getByUserId,
getOneById,
getOneByBoardIdAndUserId,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,46 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => BoardSubscription.find(criteria).sort('id');
/* Query methods */
const createOne = (values) => BoardSubscription.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByBoardId = (boardId, { exceptUserIdOrIds } = {}) => {
const criteria = {
boardId,
};
if (exceptUserIdOrIds) {
criteria.userId = {
'!=': exceptUserIdOrIds,
};
}
return defaultFind(criteria);
};
const getOneByBoardIdAndUserId = (boardId, userId) =>
BoardSubscription.findOne({
boardId,
userId,
});
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => BoardSubscription.destroy(criteria).fetch();
const deleteOne = (criteria) => BoardSubscription.destroyOne(criteria);
module.exports = {
createOne,
getByIds,
getByBoardId,
getOneByBoardIdAndUserId,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,224 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const LIMIT = 50;
const SEARCH_PARTS_REGEX = /[ ,;]+/;
const defaultFind = (criteria, { sort = 'id', limit } = {}) =>
Card.find(criteria).sort(sort).limit(limit);
/* Query methods */
const getIdsByEndlessListId = async (
listId,
{ before, search, filterUserIds, filterLabelIds } = {},
) => {
if (filterUserIds && filterUserIds.length === 0) {
return [];
}
if (filterLabelIds && filterLabelIds.length === 0) {
return [];
}
const queryValues = [];
let query = 'SELECT DISTINCT card.id FROM card';
if (filterUserIds) {
query += ' JOIN card_membership ON card.id = card_membership.card_id';
}
if (filterLabelIds) {
query += ' JOIN card_label ON card.id = card_label.card_id';
}
queryValues.push(listId);
query += ` WHERE card.list_id = $${queryValues.length}`;
if (before) {
queryValues.push(before.listChangedAt);
query += ` AND (card.list_changed_at < $${queryValues.length} OR (card.list_changed_at = $${queryValues.length}`;
queryValues.push(before.id);
query += ` AND card.id < $${queryValues.length}))`;
}
if (search) {
if (search.startsWith('/')) {
queryValues.push(search.substring(1));
query += ` AND (card.name ~* $${queryValues.length} OR card.description ~* $${queryValues.length})`;
} else {
const searchParts = search.split(SEARCH_PARTS_REGEX).flatMap((searchPart) => {
if (!searchPart) {
return [];
}
return searchPart.toLowerCase();
});
if (searchParts.length > 0) {
let ilikeValues = searchParts.map((searchPart) => {
queryValues.push(searchPart);
return `'%' || $${queryValues.length} || '%'`;
});
query += ` AND ((card.name ILIKE ALL(ARRAY[${ilikeValues.join(', ')}]))`;
ilikeValues = searchParts.map((searchPart) => {
queryValues.push(searchPart);
return `'%' || $${queryValues.length} || '%'`;
});
query += ` OR (card.description ILIKE ALL(ARRAY[${ilikeValues.join(', ')}])))`;
}
}
}
if (filterUserIds) {
const inValues = filterUserIds.map((filterUserId) => {
queryValues.push(filterUserId);
return `$${queryValues.length}`;
});
query += ` AND card_membership.user_id IN (${inValues.join(', ')})`;
}
if (filterLabelIds) {
const inValues = filterLabelIds.map((filterLabelId) => {
queryValues.push(filterLabelId);
return `$${queryValues.length}`;
});
query += ` AND card_label.label_id IN (${inValues.join(', ')})`;
}
query += ` LIMIT ${LIMIT}`;
let queryResult;
try {
queryResult = await sails.sendNativeQuery(query, queryValues);
} catch (error) {
if (
error.code === 'E_QUERY_FAILED' &&
error.message.includes('Query failed: invalid regular expression')
) {
return [];
}
throw error;
}
return sails.helpers.utils.mapRecords(queryResult.rows);
};
const createOne = (values) => Card.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByBoardId = (boardId) =>
defaultFind({
boardId,
});
const getByListId = async (listId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {
const criteria = {
listId,
};
if (exceptIdOrIds) {
criteria.id = {
'!=': exceptIdOrIds,
};
}
return defaultFind(criteria, { sort });
};
const getByEndlessListId = async (listId, { before, search, filterUserIds, filterLabelIds }) => {
const criteria = {};
const options = {
sort: ['listChangedAt DESC', 'id DESC'],
};
if (search || filterUserIds || filterLabelIds) {
criteria.id = await getIdsByEndlessListId(listId, {
before,
search,
filterUserIds,
filterLabelIds,
});
} else {
criteria.and = [{ listId }];
if (before) {
criteria.and.push({
or: [
{
listChangedAt: {
'<': before.listChangedAt,
},
},
{
listChangedAt: before.listChangedAt,
id: {
'<': before.id,
},
},
],
});
}
options.limit = LIMIT;
}
return defaultFind(criteria, options);
};
const getByListIds = async (listIds, { sort = ['position', 'id'] } = {}) =>
defaultFind(
{
listId: listIds,
},
{ sort },
);
const getOneById = (id, { listId } = {}) => {
const criteria = {
id,
};
if (listId) {
criteria.listId = listId;
}
return Card.findOne(criteria);
};
const update = (criteria, values) => Card.update(criteria).set(values).fetch();
const updateOne = (criteria, values) => Card.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => Card.destroy(criteria).fetch();
const deleteOne = (criteria) => Card.destroyOne(criteria);
module.exports = {
getIdsByEndlessListId,
createOne,
getByIds,
getByBoardId,
getByListId,
getByEndlessListId,
getByListIds,
getOneById,
update,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,46 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => CardLabel.find(criteria).sort('id');
/* Query methods */
const create = (arrayOfValues) => CardLabel.createEach(arrayOfValues).fetch();
const createOne = (values) => CardLabel.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByCardId = (cardId) =>
defaultFind({
cardId,
});
const getByCardIds = (cardIds) =>
defaultFind({
cardId: cardIds,
});
const getOneByCardIdAndLabelId = (cardId, labelId) =>
CardLabel.findOne({
cardId,
labelId,
});
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => CardLabel.destroy(criteria).fetch();
const deleteOne = (criteria) => CardLabel.destroyOne(criteria);
module.exports = {
create,
createOne,
getByIds,
getByCardId,
getByCardIds,
getOneByCardIdAndLabelId,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,46 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => CardMembership.find(criteria).sort('id');
/* Query methods */
const create = (arrayOfValues) => CardMembership.createEach(arrayOfValues).fetch();
const createOne = (values) => CardMembership.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByCardId = (cardId) =>
defaultFind({
cardId,
});
const getByCardIds = (cardIds) =>
defaultFind({
cardId: cardIds,
});
const getOneByCardIdAndUserId = (cardId, userId) =>
CardMembership.findOne({
cardId,
userId,
});
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => CardMembership.destroy(criteria).fetch();
const deleteOne = (criteria) => CardMembership.destroyOne(criteria);
module.exports = {
create,
createOne,
getByIds,
getByCardId,
getByCardIds,
getOneByCardIdAndUserId,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,53 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => CardSubscription.find(criteria).sort('id');
/* Query methods */
const createOne = (values) => CardSubscription.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByCardId = (cardId, { exceptUserIdOrIds } = {}) => {
const criteria = {
cardId,
};
if (exceptUserIdOrIds) {
criteria.userId = {
'!=': exceptUserIdOrIds,
};
}
return defaultFind(criteria);
};
const getByCardIdsAndUserId = (cardIds, userId) =>
defaultFind({
userId,
cardId: cardIds,
});
const getOneByCardIdAndUserId = (cardId, userId) =>
CardSubscription.findOne({
cardId,
userId,
});
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => CardSubscription.destroy(criteria).fetch();
const deleteOne = (criteria) => CardSubscription.destroyOne(criteria);
module.exports = {
createOne,
getByIds,
getByCardId,
getByCardIdsAndUserId,
getOneByCardIdAndUserId,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,51 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const LIMIT = 50;
const defaultFind = (criteria, { limit } = {}) =>
Comment.find(criteria).sort('id DESC').limit(limit);
/* Query methods */
const createOne = (values) => Comment.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByCardId = (cardId, { beforeId } = {}) => {
const criteria = {
cardId,
};
if (beforeId) {
criteria.id = {
'<': beforeId,
};
}
return defaultFind(criteria, { limit: LIMIT });
};
const getOneById = (id) => Comment.findOne(id);
const update = (criteria, values) => Comment.update(criteria).set(values).fetch();
const updateOne = (criteria, values) => Comment.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => Comment.destroy(criteria).fetch();
const deleteOne = (criteria) => Comment.destroyOne(criteria);
module.exports = {
createOne,
getByIds,
getByCardId,
getOneById,
update,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,87 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria, { exceptIdOrIds, sort = 'id' } = {}) => {
if (exceptIdOrIds) {
// eslint-disable-next-line no-param-reassign
criteria.id = {
'!=': exceptIdOrIds,
};
}
return CustomField.find(criteria).sort(sort);
};
/* Query methods */
const create = (arrayOfValues) => CustomField.createEach(arrayOfValues).fetch();
const createOne = (values) => CustomField.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByBaseCustomFieldGroupId = (
baseCustomFieldGroupId,
{ exceptIdOrIds, sort = ['position', 'id'] } = {},
) =>
defaultFind(
{
baseCustomFieldGroupId,
},
{ exceptIdOrIds, sort },
);
const getByBaseCustomFieldGroupIds = (
baseCustomFieldGroupIds,
{ sort = ['position', 'id'] } = {},
) =>
defaultFind(
{
baseCustomFieldGroupId: baseCustomFieldGroupIds,
},
{ sort },
);
const getByCustomFieldGroupId = async (
customFieldGroupId,
{ exceptIdOrIds, sort = ['position', 'id'] } = {},
) =>
defaultFind(
{
customFieldGroupId,
},
{ exceptIdOrIds, sort },
);
const getByCustomFieldGroupIds = async (customFieldGroupIds, { sort = ['position', 'id'] } = {}) =>
defaultFind(
{
customFieldGroupId: customFieldGroupIds,
},
{ sort },
);
const getOneById = (id) => CustomField.findOne(id);
const updateOne = (criteria, values) => CustomField.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => CustomField.destroy(criteria).fetch();
const deleteOne = (criteria) => CustomField.destroyOne(criteria);
module.exports = {
create,
createOne,
getByIds,
getByBaseCustomFieldGroupId,
getByBaseCustomFieldGroupIds,
getByCustomFieldGroupId,
getByCustomFieldGroupIds,
getOneById,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,72 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria, { exceptIdOrIds, sort = 'id' } = {}) => {
if (exceptIdOrIds) {
// eslint-disable-next-line no-param-reassign
criteria.id = {
'!=': exceptIdOrIds,
};
}
return CustomFieldGroup.find(criteria).sort(sort);
};
/* Query methods */
const create = (arrayOfValues) => CustomFieldGroup.createEach(arrayOfValues).fetch();
const createOne = (values) => CustomFieldGroup.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByBoardId = (boardId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) =>
defaultFind(
{
boardId,
},
{ exceptIdOrIds, sort },
);
const getByCardId = (cardId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) =>
defaultFind(
{
cardId,
},
{ exceptIdOrIds, sort },
);
const getByCardIds = (cardIds, { sort = ['position', 'id'] } = {}) =>
defaultFind(
{
cardId: cardIds,
},
{ sort },
);
const getOneById = (id) => CustomFieldGroup.findOne(id);
const update = (criteria, values) => CustomFieldGroup.update(criteria).set(values).fetch();
const updateOne = (criteria, values) => CustomFieldGroup.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => CustomFieldGroup.destroy(criteria).fetch();
const deleteOne = (criteria) => CustomFieldGroup.destroyOne(criteria);
module.exports = {
create,
createOne,
getByBoardId,
getByIds,
getByCardId,
getByCardIds,
getOneById,
update,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,95 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => CustomFieldValue.find(criteria).sort('id');
/* Query methods */
const create = (arrayOfValues) => CustomFieldValue.createEach(arrayOfValues).fetch();
const createOrUpdateOne = async (values) => {
const query = `
INSERT INTO custom_field_value (card_id, custom_field_group_id, custom_field_id, content, created_at)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (card_id, custom_field_group_id, custom_field_id)
DO UPDATE SET content = EXCLUDED.content, updated_at = EXCLUDED.created_at
RETURNING *
`;
const queryResult = await sails.sendNativeQuery(query, [
values.cardId,
values.customFieldGroupId,
values.customFieldId,
values.content,
new Date().toISOString(),
]);
const [customFieldValue] = queryResult.rows;
return {
id: customFieldValue.id,
cardId: customFieldValue.card_id,
customFieldGroupId: customFieldValue.custom_field_group_id,
customFieldId: customFieldValue.custom_field_id,
content: customFieldValue.content,
createdAt: customFieldValue.created_at,
updatedAt: customFieldValue.updated_at,
};
};
const getByIds = (ids) => defaultFind(ids);
const getByCardId = (cardId, { customFieldGroupIdOrIds } = {}) => {
const criteria = {
cardId,
};
if (customFieldGroupIdOrIds) {
criteria.customFieldGroupId = customFieldGroupIdOrIds;
}
return defaultFind(criteria);
};
const getByCardIds = (cardIds) =>
defaultFind({
cardId: cardIds,
});
const getByCustomFieldGroupId = (customFieldGroupId) =>
defaultFind({
customFieldGroupId,
});
const getOneByCardIdAndCustomFieldGroupIdAndCustomFieldId = (
cardId,
customFieldGroupId,
customFieldId,
) =>
CustomFieldValue.findOne({
cardId,
customFieldGroupId,
customFieldId,
});
const updateOne = (criteria, values) => CustomFieldValue.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => CustomFieldValue.destroy(criteria).fetch();
const deleteOne = (criteria) => CustomFieldValue.destroyOne(criteria);
module.exports = {
create,
createOrUpdateOne,
getByIds,
getByCardId,
getByCardIds,
getByCustomFieldGroupId,
getOneByCardIdAndCustomFieldGroupIdAndCustomFieldId,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,21 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const createOne = (values) => IdentityProviderUser.create({ ...values }).fetch();
const getOneByIssuerAndSub = (issuer, sub) =>
IdentityProviderUser.findOne({
issuer,
sub,
});
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => IdentityProviderUser.destroy(criteria).fetch();
module.exports = {
createOne,
getOneByIssuerAndSub,
delete: delete_,
};

View file

@ -0,0 +1,55 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria, { sort = 'id' } = {}) => Label.find(criteria).sort(sort);
/* Query methods */
const createOne = (values) => Label.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByBoardId = (boardId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {
const criteria = {
boardId,
};
if (exceptIdOrIds) {
criteria.id = {
'!=': exceptIdOrIds,
};
}
return defaultFind(criteria, { sort });
};
const getOneById = (id, { boardId } = {}) => {
const criteria = {
id,
};
if (boardId) {
criteria.boardId = boardId;
}
return Label.findOne(criteria);
};
const updateOne = (criteria, values) => Label.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => Label.destroy(criteria).fetch();
const deleteOne = (criteria) => Label.destroyOne(criteria);
module.exports = {
createOne,
getByIds,
getByBoardId,
getOneById,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,66 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria, { sort = 'id' } = {}) => List.find(criteria).sort(sort);
/* Query methods */
const createOne = (values) => List.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByBoardId = (boardId, { exceptIdOrIds, typeOrTypes, sort = ['position', 'id'] } = {}) => {
const criteria = {
boardId,
};
if (exceptIdOrIds) {
criteria.id = {
'!=': exceptIdOrIds,
};
}
if (typeOrTypes) {
criteria.type = typeOrTypes;
}
return defaultFind(criteria, { sort });
};
const getOneById = (id, { boardId } = {}) => {
const criteria = {
id,
};
if (boardId) {
criteria.boardId = boardId;
}
return List.findOne(criteria);
};
const getOneTrashByBoardId = (boardId) =>
List.findOne({
boardId,
type: List.Types.TRASH,
});
const updateOne = (criteria, values) => List.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => List.destroy(criteria).fetch();
const deleteOne = (criteria) => List.destroyOne(criteria);
module.exports = {
createOne,
getByIds,
getByBoardId,
getOneById,
getOneTrashByBoardId,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,76 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const LIMIT = 100;
const defaultFind = (criteria) => Notification.find(criteria).sort('id DESC');
/* Query methods */
const createOne = (values) => {
if (values.userId) {
return sails.getDatastore().transaction(async (db) => {
const notification = await Notification.create({ ...values })
.fetch()
.usingConnection(db);
const query = `
WITH exceeded_notification AS (
SELECT id
FROM notification
WHERE user_id = $1 AND is_read = FALSE
ORDER BY id DESC
OFFSET $2
)
UPDATE notification
SET is_read = TRUE
WHERE id in (SELECT id FROM exceeded_notification)
`;
await sails.sendNativeQuery(query, [values.userId, LIMIT]).usingConnection(db);
return notification;
});
}
return Notification.create({ ...values }).fetch();
};
const getByIds = (ids) => defaultFind(ids);
const getUnreadByUserId = (userId) =>
defaultFind({
userId,
isRead: false,
});
const getOneById = (id, { userId } = {}) => {
const criteria = {
id,
};
if (userId) {
criteria.userId = userId;
}
return Notification.findOne(criteria);
};
const update = (criteria, values) => Notification.update(criteria).set(values).fetch();
const updateOne = (criteria, values) => Notification.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => Notification.destroy(criteria).fetch();
module.exports = {
createOne,
getByIds,
getUnreadByUserId,
getOneById,
update,
updateOne,
delete: delete_,
};

View file

@ -0,0 +1,45 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => NotificationService.find(criteria).sort('id');
/* Query methods */
const createOne = (values) => NotificationService.create({ ...values }).fetch();
const getByUserId = (userId) =>
defaultFind({
userId,
});
const getByBoardId = (boardId) =>
defaultFind({
boardId,
});
const getByBoardIds = (boardIds) =>
defaultFind({
boardId: boardIds,
});
const getOneById = (id) => NotificationService.findOne(id);
const updateOne = (criteria, values) => NotificationService.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => NotificationService.destroy(criteria).fetch();
const deleteOne = (criteria) => NotificationService.destroyOne(criteria);
module.exports = {
createOne,
getByUserId,
getByBoardId,
getByBoardIds,
getOneById,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,67 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => Project.find(criteria).sort('id');
/* Query methods */
const createOne = (values, { user } = {}) =>
sails.getDatastore().transaction(async (db) => {
let project = await Project.create({ ...values })
.fetch()
.usingConnection(db);
const projectManager = await ProjectManager.create({
projectId: project.id,
userId: user.id,
})
.fetch()
.usingConnection(db);
if (values.type === Project.Types.PRIVATE) {
project = await Project.updateOne(project.id)
.set({
ownerProjectManagerId: projectManager.id,
})
.usingConnection(db);
}
return { project, projectManager };
});
const getByIds = (ids) => defaultFind(ids);
const getShared = ({ exceptIdOrIds } = {}) => {
const criteria = {
ownerProjectManagerId: null,
};
if (exceptIdOrIds) {
criteria.id = {
'!=': exceptIdOrIds,
};
}
return defaultFind(criteria);
};
const getOneById = (id) => Project.findOne(id);
const updateOne = (criteria, values) => Project.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => Project.destroy(criteria).fetch();
const deleteOne = (criteria) => Project.destroyOne(criteria);
module.exports = {
createOne,
getByIds,
getShared,
getOneById,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,35 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => ProjectFavorite.find(criteria).sort('id');
/* Query methods */
const createOne = (values) => ProjectFavorite.create({ ...values }).fetch();
const getByProjectIdsAndUserId = (projectIds, userId) =>
defaultFind({
userId,
projectId: projectIds,
});
const getOneByProjectIdAndUserId = (projectId, userId) =>
ProjectFavorite.findOne({
projectId,
userId,
});
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => ProjectFavorite.destroy(criteria).fetch();
const deleteOne = (criteria) => ProjectFavorite.destroyOne(criteria);
module.exports = {
createOne,
getByProjectIdsAndUserId,
getOneByProjectIdAndUserId,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,80 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => ProjectManager.find(criteria).sort('id');
/* Query methods */
const createOne = (values) => ProjectManager.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByProjectId = (projectId, { exceptIdOrIds } = {}) => {
const criteria = {
projectId,
};
if (exceptIdOrIds) {
criteria.id = {
'!=': exceptIdOrIds,
};
}
return defaultFind(criteria);
};
const getByProjectIds = (projectIds, { exceptUserIdOrIds } = {}) => {
const criteria = {
projectId: projectIds,
};
if (exceptUserIdOrIds) {
criteria.userId = {
'!=': exceptUserIdOrIds,
};
}
return defaultFind(criteria);
};
const getByUserId = (userId) =>
defaultFind({
userId,
});
const getOneById = (id, { projectId } = {}) => {
const criteria = {
id,
};
if (projectId) {
criteria.projectId = projectId;
}
return ProjectManager.findOne(criteria);
};
const getOneByProjectIdAndUserId = (projectId, userId) =>
ProjectManager.findOne({
projectId,
userId,
});
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => ProjectManager.destroy(criteria).fetch();
const deleteOne = (criteria) => ProjectManager.destroyOne(criteria);
module.exports = {
createOne,
getByIds,
getByProjectId,
getByProjectIds,
getByUserId,
getOneById,
getOneByProjectIdAndUserId,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,30 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const createOne = (values) => Session.create({ ...values }).fetch();
const getOneUndeletedByAccessToken = (accessToken) =>
Session.findOne({
accessToken,
deletedAt: null,
});
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => Session.destroy(criteria).fetch();
const deleteOneById = (id) =>
Session.updateOne({
id,
deletedAt: null,
}).set({
deletedAt: new Date().toISOString(),
});
module.exports = {
createOne,
getOneUndeletedByAccessToken,
deleteOneById,
delete: delete_,
};

View file

@ -0,0 +1,71 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria, { sort = 'id', limit } = {}) =>
Task.find(criteria).sort(sort).limit(limit);
/* Query methods */
const create = (arrayOfValues) => Task.createEach(arrayOfValues).fetch();
const createOne = (values) => Task.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByTaskListId = async (taskListId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {
const criteria = {
taskListId,
};
if (exceptIdOrIds) {
criteria.id = {
'!=': exceptIdOrIds,
};
}
return defaultFind(criteria, { sort });
};
const getByTaskListIds = async (taskListIds, { sort = ['position', 'id'] } = {}) =>
defaultFind(
{
taskListId: taskListIds,
},
{ sort },
);
const getOneById = (id, { taskListId } = {}) => {
const criteria = {
id,
};
if (taskListId) {
criteria.taskListId = taskListId;
}
return Task.findOne(criteria);
};
const update = (criteria, values) => Task.update(criteria).set(values).fetch();
const updateOne = (criteria, values) => Task.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => Task.destroy(criteria).fetch();
const deleteOne = (criteria) => Task.destroyOne(criteria);
module.exports = {
create,
createOne,
getByIds,
getByTaskListId,
getByTaskListIds,
getOneById,
update,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,67 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria, { sort = 'id' } = {}) => TaskList.find(criteria).sort(sort);
/* Query methods */
const create = (arrayOfValues) => TaskList.createEach(arrayOfValues).fetch();
const createOne = (values) => TaskList.create({ ...values }).fetch();
const getByIds = (ids) => defaultFind(ids);
const getByCardId = (cardId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {
const criteria = {
cardId,
};
if (exceptIdOrIds) {
criteria.id = {
'!=': exceptIdOrIds,
};
}
return defaultFind(criteria, { sort });
};
const getByCardIds = (cardIds, { sort = ['position', 'id'] } = {}) =>
defaultFind(
{
cardId: cardIds,
},
{ sort },
);
const getOneById = (id, { cardId } = {}) => {
const criteria = {
id,
};
if (cardId) {
criteria.cardId = cardId;
}
return TaskList.findOne(criteria);
};
const updateOne = (criteria, values) => TaskList.updateOne(criteria).set({ ...values });
// eslint-disable-next-line no-underscore-dangle
const delete_ = (criteria) => TaskList.destroy(criteria).fetch();
const deleteOne = (criteria) => TaskList.destroyOne(criteria);
module.exports = {
create,
createOne,
getByIds,
getByCardId,
getByCardIds,
getOneById,
updateOne,
deleteOne,
delete: delete_,
};

View file

@ -0,0 +1,98 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const defaultFind = (criteria) => User.find(criteria).sort('id');
/* Query methods */
const createOne = (values) => {
if (sails.config.custom.activeUsersLimit) {
return sails.getDatastore().transaction(async (db) => {
const queryResult = await sails
.sendNativeQuery('SELECT NULL FROM user_account WHERE is_deactivated = $1 FOR UPDATE', [
false,
])
.usingConnection(db);
if (queryResult.rowCount >= sails.config.custom.activeUsersLimit) {
throw 'activeLimitReached';
}
return User.create({ ...values })
.fetch()
.usingConnection(db);
});
}
return User.create({ ...values }).fetch();
};
const getByIds = (ids) => defaultFind(ids);
const getAll = ({ roleOrRoles } = {}) =>
defaultFind({
role: roleOrRoles,
});
const getOneById = (id, { withDeactivated = true } = {}) => {
const criteria = {
id,
};
if (!withDeactivated) {
criteria.isDeactivated = false;
}
return User.findOne(criteria);
};
const getOneByEmail = (email) =>
User.findOne({
email: email.toLowerCase(),
});
const getOneActiveByEmailOrUsername = (emailOrUsername) => {
const fieldName = emailOrUsername.includes('@') ? 'email' : 'username';
return User.findOne({
[fieldName]: emailOrUsername.toLowerCase(),
isDeactivated: false,
});
};
const updateOne = (criteria, values) => {
if (values.isDeactivated === false && sails.config.custom.activeUsersLimit) {
return sails.getDatastore().transaction(async (db) => {
const queryResult = await sails
.sendNativeQuery('SELECT NULL FROM user_account WHERE is_deactivated = $1 FOR UPDATE', [
false,
])
.usingConnection(db);
if (queryResult.rowCount >= sails.config.custom.activeUsersLimit) {
throw 'activeLimitReached';
}
return User.updateOne(criteria)
.set({ ...values })
.usingConnection(db);
});
}
return User.updateOne(criteria).set({ ...values });
};
const deleteOne = (criteria) => User.destroyOne(criteria);
module.exports = {
createOne,
getByIds,
getAll,
getOneById,
getOneByEmail,
getOneActiveByEmailOrUsername,
updateOne,
deleteOne,
};

View file

@ -1,5 +1,7 @@
const { URL } = require('url');
const { S3Client } = require('@aws-sdk/client-s3');
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
/**
* s3 hook
@ -9,6 +11,9 @@ const { S3Client } = require('@aws-sdk/client-s3');
* @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
*/
const { URL } = require('url');
const { S3Client } = require('@aws-sdk/client-s3');
module.exports = function defineS3Hook(sails) {
let client = null;
@ -18,7 +23,7 @@ module.exports = function defineS3Hook(sails) {
*/
async initialize() {
if (!sails.config.custom.s3Endpoint && !sails.config.custom.s3Region) {
if (!this.isEnabled()) {
return;
}
@ -57,8 +62,8 @@ module.exports = function defineS3Hook(sails) {
return `https://${sails.config.custom.s3Bucket}.s3.${sails.config.custom.s3Region}.amazonaws.com`;
},
isActive() {
return client !== null;
isEnabled() {
return !!sails.config.custom.s3Endpoint || !!sails.config.custom.s3Region;
},
};
};

View file

@ -1,4 +1,7 @@
const nodemailer = require('nodemailer');
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
/**
* smtp hook
@ -8,6 +11,8 @@ const nodemailer = require('nodemailer');
* @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
*/
const nodemailer = require('nodemailer');
module.exports = function defineSmtpHook(sails) {
let transporter = null;
@ -17,7 +22,7 @@ module.exports = function defineSmtpHook(sails) {
*/
async initialize() {
if (!sails.config.custom.smtpHost) {
if (!this.isEnabled()) {
return;
}
@ -43,8 +48,8 @@ module.exports = function defineSmtpHook(sails) {
return transporter;
},
isActive() {
return transporter !== null;
isEnabled() {
return !!sails.config.custom.smtpHost;
},
};
};

View file

@ -1,3 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
/**
* watcher hook
*
@ -8,7 +13,7 @@
module.exports = function defineWatcherHook(sails) {
const checkSocketConnectionsToLogout = () => {
Object.keys(sails.io.sockets.adapter.rooms).forEach((room) => {
[...sails.io.sockets.adapter.rooms.keys()].forEach((room) => {
if (!room.startsWith('@accessToken:')) {
return;
}