mirror of
https://github.com/plankanban/planka.git
synced 2025-07-18 12:49:43 +02:00
parent
7ef55ec578
commit
7d138b858d
10 changed files with 156 additions and 19 deletions
|
@ -57,7 +57,7 @@ COPY --from=client-builder /app/build/index.html views
|
|||
|
||||
VOLUME /app/public/user-avatars
|
||||
VOLUME /app/public/project-background-images
|
||||
VOLUME /app/public/attachments
|
||||
VOLUME /app/private/attachments
|
||||
|
||||
EXPOSE 1337
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ services:
|
|||
volumes:
|
||||
- user-avatars:/app/public/user-avatars
|
||||
- project-background-images:/app/public/project-background-images
|
||||
- attachments:/app/public/attachments
|
||||
- attachments:/app/private/attachments
|
||||
ports:
|
||||
- 3000:1337
|
||||
environment:
|
||||
|
|
8
server/.gitignore
vendored
8
server/.gitignore
vendored
|
@ -135,9 +135,11 @@ public/user-avatars/*
|
|||
!public/project-background-images
|
||||
public/project-background-images/*
|
||||
!public/project-background-images/.gitkeep
|
||||
!public/attachments
|
||||
public/attachments/*
|
||||
!public/attachments/.gitkeep
|
||||
|
||||
private/*
|
||||
!private/attachments
|
||||
private/attachments/*
|
||||
!private/attachments/.gitkeep
|
||||
|
||||
views/*
|
||||
!views/.gitkeep
|
||||
|
|
67
server/api/controllers/attachments/download-thumbnail.js
Normal file
67
server/api/controllers/attachments/download-thumbnail.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const Errors = {
|
||||
ATTACHMENT_NOT_FOUND: {
|
||||
attachmentNotFound: 'Attachment not found',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'string',
|
||||
regex: /^[0-9]+$/,
|
||||
required: true,
|
||||
},
|
||||
filename: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
exits: {
|
||||
attachmentNotFound: {
|
||||
responseType: 'notFound',
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { attachment, card, project } = await sails.helpers.attachments
|
||||
.getProjectPath(inputs.id)
|
||||
.intercept('pathNotFound', () => Errors.ATTACHMENT_NOT_FOUND);
|
||||
|
||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, card.boardId);
|
||||
|
||||
if (!isBoardMember) {
|
||||
const isProjectManager = await sails.helpers.users.isProjectManager(
|
||||
currentUser.id,
|
||||
project.id,
|
||||
);
|
||||
|
||||
if (!isProjectManager) {
|
||||
throw Errors.ATTACHMENT_NOT_FOUND; // Forbidden
|
||||
}
|
||||
}
|
||||
|
||||
if (!attachment.isImage) {
|
||||
throw Errors.ATTACHMENT_NOT_FOUND;
|
||||
}
|
||||
|
||||
const filePath = path.join(
|
||||
sails.config.custom.attachmentsPath,
|
||||
attachment.dirname,
|
||||
'thumbnails',
|
||||
inputs.filename,
|
||||
);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw Errors.ATTACHMENT_NOT_FOUND;
|
||||
}
|
||||
|
||||
this.res.setHeader('Content-Disposition', `inline; ${inputs.filename}`);
|
||||
return exits.success(fs.createReadStream(filePath));
|
||||
},
|
||||
};
|
69
server/api/controllers/attachments/download.js
Normal file
69
server/api/controllers/attachments/download.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const Errors = {
|
||||
ATTACHMENT_NOT_FOUND: {
|
||||
attachmentNotFound: 'Attachment not found',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
id: {
|
||||
type: 'string',
|
||||
regex: /^[0-9]+$/,
|
||||
required: true,
|
||||
},
|
||||
filename: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
exits: {
|
||||
attachmentNotFound: {
|
||||
responseType: 'notFound',
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs, exits) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
const { attachment, card, project } = await sails.helpers.attachments
|
||||
.getProjectPath(inputs.id)
|
||||
.intercept('pathNotFound', () => Errors.ATTACHMENT_NOT_FOUND);
|
||||
|
||||
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, card.boardId);
|
||||
|
||||
if (!isBoardMember) {
|
||||
const isProjectManager = await sails.helpers.users.isProjectManager(
|
||||
currentUser.id,
|
||||
project.id,
|
||||
);
|
||||
|
||||
if (!isProjectManager) {
|
||||
throw Errors.ATTACHMENT_NOT_FOUND; // Forbidden
|
||||
}
|
||||
}
|
||||
|
||||
const filePath = path.join(
|
||||
sails.config.custom.attachmentsPath,
|
||||
attachment.dirname,
|
||||
attachment.filename,
|
||||
);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw Errors.ATTACHMENT_NOT_FOUND;
|
||||
}
|
||||
|
||||
let contentDisposition;
|
||||
if (attachment.isImage || path.extname(attachment.filename) === '.pdf') {
|
||||
contentDisposition = 'inline';
|
||||
} else {
|
||||
contentDisposition = `attachment; ${inputs.filename}`;
|
||||
}
|
||||
|
||||
this.res.setHeader('Content-Disposition', contentDisposition);
|
||||
return exits.success(fs.createReadStream(filePath));
|
||||
},
|
||||
};
|
|
@ -52,9 +52,9 @@ module.exports = {
|
|||
customToJSON() {
|
||||
return {
|
||||
..._.omit(this, ['dirname', 'filename', 'isImage']),
|
||||
url: `${sails.config.custom.attachmentsUrl}/${this.dirname}/${this.filename}`,
|
||||
url: `${sails.config.custom.attachmentsUrl}/${this.id}/download/${this.filename}`,
|
||||
coverUrl: this.isImage
|
||||
? `${sails.config.custom.attachmentsUrl}/${this.dirname}/thumbnails/cover-256.jpg`
|
||||
? `${sails.config.custom.attachmentsUrl}/${this.id}/download/thumbnails/cover-256.jpg`
|
||||
: null,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -26,6 +26,6 @@ module.exports.custom = {
|
|||
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'),
|
||||
attachmentsPath: path.join(sails.config.appPath, 'private', 'attachments'),
|
||||
attachmentsUrl: `${process.env.BASE_URL}/attachments`,
|
||||
};
|
||||
|
|
|
@ -18,21 +18,10 @@ module.exports.policies = {
|
|||
|
||||
'*': 'is-authenticated',
|
||||
|
||||
// 'users/index': ['is-authenticated', 'is-admin'],
|
||||
'users/create': ['is-authenticated', 'is-admin'],
|
||||
'users/delete': ['is-authenticated', 'is-admin'],
|
||||
|
||||
'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'],
|
||||
// 'project-memberships/delete': ['is-authenticated', 'is-admin'],
|
||||
|
||||
// 'boards/create': ['is-authenticated', 'is-admin'],
|
||||
// 'boards/update': ['is-authenticated', 'is-admin'],
|
||||
// 'boards/delete': ['is-authenticated', 'is-admin'],
|
||||
|
||||
'access-tokens/create': true,
|
||||
};
|
||||
|
|
|
@ -75,6 +75,16 @@ module.exports.routes = {
|
|||
'GET /api/notifications/:id': 'notifications/show',
|
||||
'PATCH /api/notifications/:ids': 'notifications/update',
|
||||
|
||||
'GET /attachments/:id/download/:filename': {
|
||||
action: 'attachments/download',
|
||||
skipAssets: false,
|
||||
},
|
||||
|
||||
'GET /attachments/:id/download/thumbnails/:filename': {
|
||||
action: 'attachments/download-thumbnail',
|
||||
skipAssets: false,
|
||||
},
|
||||
|
||||
'GET /*': {
|
||||
view: 'index',
|
||||
skipAssets: true,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue