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

Add project backgrounds

This commit is contained in:
Maksim Eltyshev 2020-05-26 00:46:04 +05:00
parent af4297ac62
commit 3bb68b0d4f
67 changed files with 774 additions and 210 deletions

3
server/.gitignore vendored
View file

@ -131,6 +131,9 @@ public/*
!public/user-avatars
public/user-avatars/*
!public/user-avatars/.gitkeep
!public/project-background-images
public/project-background-images/*
!public/project-background-images/.gitkeep
!public/attachments
public/attachments/*
!public/attachments/.gitkeep

View file

@ -0,0 +1,60 @@
const Errors = {
PROJECT_NOT_FOUND: {
projectNotFound: 'Project not found',
},
};
module.exports = {
inputs: {
id: {
type: 'string',
regex: /^[0-9]+$/,
required: true,
},
},
exits: {
projectNotFound: {
responseType: 'notFound',
},
uploadError: {
responseType: 'unprocessableEntity',
},
},
async fn(inputs, exits) {
let project = await Project.findOne(inputs.id);
if (!project) {
throw Errors.PROJECT_NOT_FOUND;
}
this.req
.file('file')
.upload(sails.helpers.createProjectBackgroundImageReceiver(), async (error, files) => {
if (error) {
return exits.uploadError(error.message);
}
if (files.length === 0) {
return exits.uploadError('No file was uploaded');
}
project = await sails.helpers.updateProject(
project,
{
backgroundImageDirname: files[0].extra.dirname,
},
this.req,
);
if (!project) {
throw Errors.PROJECT_NOT_FOUND;
}
return exits.success({
item: project.toJSON(),
});
});
},
};

View file

@ -15,6 +15,14 @@ module.exports = {
type: 'string',
isNotEmptyString: true,
},
background: {
type: 'json',
custom: (value) => _.isNull(value),
},
backgroundImage: {
type: 'json',
custom: (value) => _.isNull(value),
},
},
exits: {
@ -30,7 +38,7 @@ module.exports = {
throw Errors.PROJECT_NOT_FOUND;
}
const values = _.pick(inputs, ['name']);
const values = _.pick(inputs, ['name', 'background', 'backgroundImage']);
project = await sails.helpers.updateProject(project, values, this.req);

View file

@ -38,7 +38,7 @@ module.exports = {
user = currentUser;
}
this.req.file('file').upload(sails.helpers.createAvatarReceiver(), async (error, files) => {
this.req.file('file').upload(sails.helpers.createUserAvatarReceiver(), async (error, files) => {
if (error) {
return exits.uploadError(error.message);
}
@ -60,7 +60,7 @@ module.exports = {
}
return exits.success({
item: user.toJSON().avatarUrl,
item: user.toJSON(),
});
});
},

View file

@ -77,7 +77,7 @@ module.exports = {
}
return exits.success({
item: user.email,
item: user,
});
},
};

View file

@ -68,7 +68,7 @@ module.exports = {
}
return exits.success({
item: null,
item: user,
});
},
};

View file

@ -79,7 +79,7 @@ module.exports = {
}
return exits.success({
item: user.username,
item: user,
});
},
};

View file

@ -0,0 +1,58 @@
const fs = require('fs');
const path = require('path');
const util = require('util');
const stream = require('stream');
const streamToArray = require('stream-to-array');
const { v4: uuid } = require('uuid');
const sharp = require('sharp');
const writeFile = util.promisify(fs.writeFile);
module.exports = {
sync: true,
fn(inputs, exits) {
const receiver = stream.Writable({
objectMode: true,
});
let firstFileHandled = false;
// eslint-disable-next-line no-underscore-dangle
receiver._write = async (file, receiverEncoding, done) => {
if (firstFileHandled) {
file.pipe(new stream.Writable());
return done();
}
firstFileHandled = true;
const buffer = await streamToArray(file).then((parts) =>
Buffer.concat(parts.map((part) => (util.isBuffer(part) ? part : Buffer.from(part)))),
);
try {
const originalBuffer = await sharp(buffer).jpeg().toBuffer();
const cover336Buffer = await sharp(buffer).resize(336, 200).jpeg().toBuffer();
const dirname = uuid();
const rootPath = path.join(sails.config.custom.projectBackgroundImagesPath, dirname);
fs.mkdirSync(rootPath);
await writeFile(path.join(rootPath, 'original.jpg'), originalBuffer);
await writeFile(path.join(rootPath, 'cover-336.jpg'), cover336Buffer);
// eslint-disable-next-line no-param-reassign
file.extra = {
dirname,
};
return done();
} catch (error) {
return done(error);
}
};
return exits.success(receiver);
},
};

View file

@ -31,6 +31,7 @@ module.exports = {
);
try {
const originalBuffer = await sharp(buffer).jpeg().toBuffer();
const square100Buffer = await sharp(buffer).resize(100, 100).jpeg().toBuffer();
const dirname = uuid();
@ -38,7 +39,7 @@ module.exports = {
const rootPath = path.join(sails.config.custom.userAvatarsPath, dirname);
fs.mkdirSync(rootPath);
await writeFile(path.join(rootPath, 'original.jpg'), buffer);
await writeFile(path.join(rootPath, 'original.jpg'), originalBuffer);
await writeFile(path.join(rootPath, 'square-100.jpg'), square100Buffer);
// eslint-disable-next-line no-param-reassign

View file

@ -1,3 +1,6 @@
const path = require('path');
const rimraf = require('rimraf');
module.exports = {
inputs: {
record: {
@ -6,6 +9,10 @@ module.exports = {
},
values: {
type: 'json',
custom: (value) =>
_.isPlainObject(value) &&
(_.isUndefined(value.background) || _.isNull(value.background)) &&
(_.isUndefined(value.backgroundImage) || _.isNull(value.backgroundImage)),
required: true,
},
request: {
@ -14,9 +21,39 @@ module.exports = {
},
async fn(inputs, exits) {
if (!_.isUndefined(inputs.values.backgroundImage)) {
/* eslint-disable no-param-reassign */
inputs.values.backgroundImageDirname = null;
delete inputs.values.backgroundImage;
/* eslint-enable no-param-reassign */
}
if (inputs.values.backgroundImageDirname) {
// eslint-disable-next-line no-param-reassign
inputs.values.background = {
type: 'image',
};
}
const project = await Project.updateOne(inputs.record.id).set(inputs.values);
if (project) {
if (
inputs.record.backgroundImageDirname &&
project.backgroundImageDirname !== inputs.record.backgroundImageDirname
) {
try {
rimraf.sync(
path.join(
sails.config.custom.projectBackgroundImagesPath,
inputs.record.backgroundImageDirname,
),
);
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
}
const userIds = await sails.helpers.getMembershipUserIdsForProject(project.id);
userIds.forEach((userId) => {

View file

@ -15,6 +15,15 @@ module.exports = {
type: 'string',
required: true,
},
background: {
type: 'json',
},
backgroundImageDirname: {
type: 'string',
isNotEmptyString: true,
allowNull: true,
columnName: 'background_image_dirname',
},
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
@ -34,4 +43,14 @@ module.exports = {
via: 'projectId',
},
},
customToJSON() {
return {
..._.omit(this, ['backgroundImageDirname']),
backgroundImage: this.backgroundImageDirname && {
url: `${sails.config.custom.projectBackgroundImagesUrl}/${this.backgroundImageDirname}/original.jpg`,
coverUrl: `${sails.config.custom.projectBackgroundImagesUrl}/${this.backgroundImageDirname}/cover-336.jpg`,
},
};
},
};

View file

@ -23,6 +23,9 @@ module.exports.custom = {
userAvatarsPath: path.join(sails.config.paths.public, 'user-avatars'),
userAvatarsUrl: `${process.env.BASE_URL}/user-avatars`,
projectBackgroundImagesPath: path.join(sails.config.paths.public, 'project-background-images'),
projectBackgroundImagesUrl: `${process.env.BASE_URL}/project-background-images`,
attachmentsPath: path.join(sails.config.paths.public, 'attachments'),
attachmentsUrl: `${process.env.BASE_URL}/attachments`,
};

View file

@ -332,6 +332,9 @@ module.exports = {
userAvatarsPath: path.join(sails.config.paths.public, 'user-avatars'),
userAvatarsUrl: `${process.env.BASE_URL}/user-avatars`,
projectBackgroundImagesPath: path.join(sails.config.paths.public, 'project-background-images'),
projectBackgroundImagesUrl: `${process.env.BASE_URL}/project-background-images`,
attachmentsPath: path.join(sails.config.paths.public, 'attachments'),
attachmentsUrl: `${process.env.BASE_URL}/attachments`,
},

View file

@ -24,6 +24,7 @@ module.exports.policies = {
'projects/create': ['is-authenticated', 'is-admin'],
'projects/update': ['is-authenticated', 'is-admin'],
'projects/update-background-image': ['is-authenticated', 'is-admin'],
'projects/delete': ['is-authenticated', 'is-admin'],
'project-memberships/create': ['is-authenticated', 'is-admin'],

View file

@ -18,12 +18,13 @@ module.exports.routes = {
'PATCH /api/users/:id/email': 'users/update-email',
'PATCH /api/users/:id/password': 'users/update-password',
'PATCH /api/users/:id/username': 'users/update-username',
'POST /api/users/:id/update-avatar': 'users/update-avatar',
'POST /api/users/:id/avatar': 'users/update-avatar',
'DELETE /api/users/:id': 'users/delete',
'GET /api/projects': 'projects/index',
'POST /api/projects': 'projects/create',
'PATCH /api/projects/:id': 'projects/update',
'POST /api/projects/:id/background-image': 'projects/update-background-image',
'DELETE /api/projects/:id': 'projects/delete',
'POST /api/projects/:projectId/memberships': 'project-memberships/create',

View file

@ -5,6 +5,8 @@ module.exports.up = (knex) =>
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
table.text('name').notNullable();
table.jsonb('background');
table.text('background_image_dirname');
table.timestamp('created_at', true);
table.timestamp('updated_at', true);