1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-19 05:09:43 +02:00

support project background + user avatar

This commit is contained in:
Nguyễn Hải Quang 2024-11-10 00:26:49 +07:00
parent 84b0f91b44
commit e84a2837ab
13 changed files with 223 additions and 46 deletions

View file

@ -82,12 +82,12 @@ services:
# - TELEGRAM_THREAD_ID= # - TELEGRAM_THREAD_ID=
# Attachments S3 # Attachments S3
# - ATTACHMENTS_S3=true # - S3_ENABLE=true
# - ATTACHMENTS_S3_REGION= # - S3_REGION=
# - ATTACHMENTS_S3_ENDPOINT= # - S3_ENDPOINT=
# - ATTACHMENTS_S3_BUCKET= # - S3_BUCKET=
# - ATTACHMENTS_S3_ACCESS_KEY= # - S3_ACCESS_KEY=
# - ATTACHMENTS_S3_SECRET_KEY= # - S3_SECRET_KEY=
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy

View file

@ -76,9 +76,9 @@ SECRET_KEY=notsecretkey
TZ=UTC TZ=UTC
# ATTACHMENTS_S3=true # S3_ENABLE=true
# ATTACHMENTS_S3_REGION= # S3_REGION=
# ATTACHMENTS_S3_ENDPOINT= # S3_ENDPOINT=
# ATTACHMENTS_S3_BUCKET= # S3_BUCKET=
# ATTACHMENTS_S3_ACCESS_KEY= # S3_ACCESS_KEY=
# ATTACHMENTS_S3_SECRET_KEY= # S3_SECRET_KEY=

View file

@ -53,19 +53,26 @@ module.exports = {
try { try {
const type = attachment.type || 'local'; const type = attachment.type || 'local';
if (type === 's3') { if (type === 's3') {
const client = await sails.helpers.attachments.getSimpleStorageServiceClient(); const client = await sails.helpers.utils.getSimpleStorageServiceClient();
if (client) { if (client) {
const file1 = `${attachment.dirname}/${attachment.filename}`; if (attachment.url) {
const file2 = `${attachment.dirname}/thumbnails/cover-256.png`; const parsedUrl = new URL(attachment.url);
await client.delete({ Key: file1 }); await client.delete({ Key: parsedUrl.pathname.replace(/^\/+/, '') });
await client.delete({ Key: file2 }); }
if (attachment.thumb) {
const parsedUrl = new URL(attachment.thumb);
await client.delete({ Key: parsedUrl.pathname.replace(/^\/+/, '') });
}
} }
} else {
rimraf.sync(path.join(sails.config.custom.attachmentsPath, attachment.dirname));
} }
} catch (error) { } catch (error) {
console.warn(error.stack); // eslint-disable-line no-console console.warn(error.stack); // eslint-disable-line no-console
} }
try {
rimraf.sync(path.join(sails.config.custom.attachmentsPath, attachment.dirname));
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
sails.sockets.broadcast( sails.sockets.broadcast(
`board:${inputs.board.id}`, `board:${inputs.board.id}`,

View file

@ -23,11 +23,11 @@ module.exports = {
const rootPath = path.join(sails.config.custom.attachmentsPath, dirname); const rootPath = path.join(sails.config.custom.attachmentsPath, dirname);
const filePath = path.join(rootPath, filename); const filePath = path.join(rootPath, filename);
if (sails.config.custom.attachmentsS3) { if (sails.config.custom.s3Config) {
const client = await sails.helpers.attachments.getSimpleStorageServiceClient(); const client = await sails.helpers.utils.getSimpleStorageServiceClient();
const s3Image = await client.upload({ const s3Image = await client.upload({
Body: fs.createReadStream(inputs.file.fd), Body: fs.createReadStream(inputs.file.fd),
Key: `${dirname}/${filename}`, Key: `attachments/${dirname}/${filename}`,
ContentType: inputs.file.type, ContentType: inputs.file.type,
}); });
@ -72,7 +72,7 @@ module.exports = {
) )
.toBuffer(); .toBuffer();
const s3Thumb = await client.upload({ const s3Thumb = await client.upload({
Key: `${dirname}/thumbnails/cover-256.${thumbnailsExtension}`, Key: `attachments/${dirname}/thumbnails/cover-256.${thumbnailsExtension}`,
Body: resizeBuffer, Body: resizeBuffer,
ContentType: inputs.file.type, ContentType: inputs.file.type,
}); });
@ -83,6 +83,12 @@ module.exports = {
} }
} }
try {
rimraf.sync(inputs.file.fd);
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
return fileData; return fileData;
} }

View file

@ -33,9 +33,6 @@ module.exports = {
} }
const dirname = uuid(); const dirname = uuid();
const rootPath = path.join(sails.config.custom.projectBackgroundImagesPath, dirname);
fs.mkdirSync(rootPath);
let { width, pageHeight: height = metadata.height } = metadata; let { width, pageHeight: height = metadata.height } = metadata;
if (metadata.orientation && metadata.orientation > 4) { if (metadata.orientation && metadata.orientation > 4) {
@ -44,6 +41,64 @@ module.exports = {
const extension = metadata.format === 'jpeg' ? 'jpg' : metadata.format; const extension = metadata.format === 'jpeg' ? 'jpg' : metadata.format;
if (sails.config.custom.s3Config) {
const client = await sails.helpers.utils.getSimpleStorageServiceClient();
let originalUrl = '';
let thumbUrl = '';
try {
const s3Original = await client.upload({
Body: await image.toBuffer(),
Key: `project-background-images/${dirname}/original.${extension}`,
ContentType: inputs.file.type,
});
originalUrl = s3Original.Location;
const resizeBuffer = await image
.resize(
336,
200,
width < 336 || height < 200
? {
kernel: sharp.kernel.nearest,
}
: undefined,
)
.toBuffer();
const s3Thumb = await client.upload({
Body: resizeBuffer,
Key: `project-background-images/${dirname}/cover-336.${extension}`,
ContentType: inputs.file.type,
});
thumbUrl = s3Thumb.Location;
} catch (error1) {
try {
client.delete({ Key: `project-background-images/${dirname}/original.${extension}` });
} catch (error2) {
console.warn(error2.stack); // eslint-disable-line no-console
}
throw 'fileIsNotImage';
}
try {
rimraf.sync(inputs.file.fd);
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
return {
dirname,
extension,
original: originalUrl,
thumb: thumbUrl,
};
}
const rootPath = path.join(sails.config.custom.projectBackgroundImagesPath, dirname);
fs.mkdirSync(rootPath);
try { try {
await image.toFile(path.join(rootPath, `original.${extension}`)); await image.toFile(path.join(rootPath, `original.${extension}`));

View file

@ -86,6 +86,21 @@ module.exports = {
(!project.backgroundImage || (!project.backgroundImage ||
project.backgroundImage.dirname !== inputs.record.backgroundImage.dirname) project.backgroundImage.dirname !== inputs.record.backgroundImage.dirname)
) { ) {
try {
if (sails.config.custom.s3Config) {
const client = await sails.helpers.utils.getSimpleStorageServiceClient();
if (client && inputs.record.backgroundImage && inputs.record.backgroundImage.original) {
const parsedUrl = new URL(inputs.record.backgroundImage.original);
await client.delete({ Key: parsedUrl.pathname.replace(/^\/+/, '') });
}
if (client && inputs.record.backgroundImage && inputs.record.backgroundImage.thumb) {
const parsedUrl = new URL(inputs.record.backgroundImage.thumb);
await client.delete({ Key: parsedUrl.pathname.replace(/^\/+/, '') });
}
}
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
try { try {
rimraf.sync( rimraf.sync(
path.join( path.join(

View file

@ -33,9 +33,6 @@ module.exports = {
} }
const dirname = uuid(); const dirname = uuid();
const rootPath = path.join(sails.config.custom.userAvatarsPath, dirname);
fs.mkdirSync(rootPath);
let { width, pageHeight: height = metadata.height } = metadata; let { width, pageHeight: height = metadata.height } = metadata;
if (metadata.orientation && metadata.orientation > 4) { if (metadata.orientation && metadata.orientation > 4) {
@ -44,6 +41,64 @@ module.exports = {
const extension = metadata.format === 'jpeg' ? 'jpg' : metadata.format; const extension = metadata.format === 'jpeg' ? 'jpg' : metadata.format;
if (sails.config.custom.s3Config) {
const client = await sails.helpers.utils.getSimpleStorageServiceClient();
let originalUrl = '';
let squareUrl = '';
try {
const s3Original = await client.upload({
Body: await image.toBuffer(),
Key: `user-avatars/${dirname}/original.${extension}`,
ContentType: inputs.file.type,
});
originalUrl = s3Original.Location;
const resizeBuffer = await image
.resize(
100,
100,
width < 100 || height < 100
? {
kernel: sharp.kernel.nearest,
}
: undefined,
)
.toBuffer();
const s3Square = await client.upload({
Body: resizeBuffer,
Key: `user-avatars/${dirname}/square-100.${extension}`,
ContentType: inputs.file.type,
});
squareUrl = s3Square.Location;
} catch (error1) {
try {
client.delete({ Key: `user-avatars/${dirname}/original.${extension}` });
} catch (error2) {
console.warn(error2.stack); // eslint-disable-line no-console
}
throw 'fileIsNotImage';
}
try {
rimraf.sync(inputs.file.fd);
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
return {
dirname,
extension,
original: originalUrl,
square: squareUrl,
};
}
const rootPath = path.join(sails.config.custom.userAvatarsPath, dirname);
fs.mkdirSync(rootPath);
try { try {
await image.toFile(path.join(rootPath, `original.${extension}`)); await image.toFile(path.join(rootPath, `original.${extension}`));

View file

@ -101,6 +101,21 @@ module.exports = {
inputs.record.avatar && inputs.record.avatar &&
(!user.avatar || user.avatar.dirname !== inputs.record.avatar.dirname) (!user.avatar || user.avatar.dirname !== inputs.record.avatar.dirname)
) { ) {
try {
if (sails.config.custom.s3Config) {
const client = await sails.helpers.utils.getSimpleStorageServiceClient();
if (client && inputs.record.avatar && inputs.record.avatar.original) {
const parsedUrl = new URL(inputs.record.avatar.original);
await client.delete({ Key: parsedUrl.pathname.replace(/^\/+/, '') });
}
if (client && inputs.record.avatar && inputs.record.avatar.square) {
const parsedUrl = new URL(inputs.record.avatar.square);
await client.delete({ Key: parsedUrl.pathname.replace(/^\/+/, '') });
}
}
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
try { try {
rimraf.sync(path.join(sails.config.custom.userAvatarsPath, inputs.record.avatar.dirname)); rimraf.sync(path.join(sails.config.custom.userAvatarsPath, inputs.record.avatar.dirname));
} catch (error) { } catch (error) {

View file

@ -37,8 +37,8 @@ class S3Client {
module.exports = { module.exports = {
fn() { fn() {
if (sails.config.custom.attachmentsS3) { if (sails.config.custom.s3Config) {
return new S3Client(sails.config.custom.attachmentsS3); return new S3Client(sails.config.custom.s3Config);
} }
return null; return null;
}, },

View file

@ -31,9 +31,11 @@ module.exports = {
}, },
url: { url: {
type: 'string', type: 'string',
allowNull: true,
}, },
thumb: { thumb: {
type: 'string', type: 'string',
allowNull: true,
}, },
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗ // ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗

View file

@ -79,11 +79,26 @@ module.exports = {
}, },
customToJSON() { customToJSON() {
let url = '';
let coverUrl = '';
if (this.backgroundImage) {
if (this.backgroundImage.original) {
url = this.backgroundImage.original;
} else {
url = `${sails.config.custom.projectBackgroundImagesUrl}/${this.backgroundImage.dirname}/original.${this.backgroundImage.extension}`;
}
if (this.backgroundImage.thumb) {
coverUrl = this.backgroundImage.thumb;
} else {
coverUrl = `${sails.config.custom.projectBackgroundImagesUrl}/${this.backgroundImage.dirname}/cover-336.${this.backgroundImage.extension}`;
}
}
return { return {
..._.omit(this, ['backgroundImage']), ..._.omit(this, ['backgroundImage']),
backgroundImage: this.backgroundImage && { backgroundImage: this.backgroundImage && {
url: `${sails.config.custom.projectBackgroundImagesUrl}/${this.backgroundImage.dirname}/original.${this.backgroundImage.extension}`, url,
coverUrl: `${sails.config.custom.projectBackgroundImagesUrl}/${this.backgroundImage.dirname}/cover-336.${this.backgroundImage.extension}`, coverUrl,
}, },
}; };
}, },

View file

@ -148,6 +148,14 @@ module.exports = {
customToJSON() { customToJSON() {
const isDefaultAdmin = this.email === sails.config.custom.defaultAdminEmail; const isDefaultAdmin = this.email === sails.config.custom.defaultAdminEmail;
let avatarUrl = '';
if (this.avatar) {
if (this.avatar.square) {
avatarUrl = this.avatar.square;
} else {
avatarUrl = `${sails.config.custom.userAvatarsUrl}/${this.avatar.dirname}/square-100.${this.avatar.extension}`;
}
}
return { return {
..._.omit(this, ['password', 'isSso', 'avatar', 'passwordChangedAt']), ..._.omit(this, ['password', 'isSso', 'avatar', 'passwordChangedAt']),
@ -155,9 +163,7 @@ module.exports = {
isRoleLocked: (this.isSso && !sails.config.custom.oidcIgnoreRoles) || isDefaultAdmin, isRoleLocked: (this.isSso && !sails.config.custom.oidcIgnoreRoles) || isDefaultAdmin,
isUsernameLocked: (this.isSso && !sails.config.custom.oidcIgnoreUsername) || isDefaultAdmin, isUsernameLocked: (this.isSso && !sails.config.custom.oidcIgnoreUsername) || isDefaultAdmin,
isDeletionLocked: isDefaultAdmin, isDeletionLocked: isDefaultAdmin,
avatarUrl: avatarUrl,
this.avatar &&
`${sails.config.custom.userAvatarsUrl}/${this.avatar.dirname}/square-100.${this.avatar.extension}`,
}; };
}, },
}; };

View file

@ -36,19 +36,20 @@ module.exports.custom = {
projectBackgroundImagesPath: path.join(sails.config.paths.public, 'project-background-images'), projectBackgroundImagesPath: path.join(sails.config.paths.public, 'project-background-images'),
projectBackgroundImagesUrl: `${process.env.BASE_URL}/project-background-images`, projectBackgroundImagesUrl: `${process.env.BASE_URL}/project-background-images`,
attachmentsS3:
process.env.ATTACHMENTS_S3 === 'true'
? {
accessKeyId: process.env.ATTACHMENTS_S3_ACCESS_KEY,
secretAccessKey: process.env.ATTACHMENTS_S3_SECRET_KEY,
region: process.env.ATTACHMENTS_S3_REGION,
endpoint: process.env.ATTACHMENTS_S3_ENDPOINT,
bucket: process.env.ATTACHMENTS_S3_BUCKET,
}
: null,
attachmentsPath: path.join(sails.config.appPath, 'private', 'attachments'), attachmentsPath: path.join(sails.config.appPath, 'private', 'attachments'),
attachmentsUrl: `${process.env.BASE_URL}/attachments`, attachmentsUrl: `${process.env.BASE_URL}/attachments`,
s3Config:
process.env.S3_ENABLE === 'true'
? {
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY,
region: process.env.S3_REGION,
endpoint: process.env.S3_ENDPOINT,
bucket: process.env.S3_BUCKET,
}
: null,
defaultAdminEmail: defaultAdminEmail:
process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase(), process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase(),