Compare commits

...
7 Commits
32 changed files with 463 additions and 344 deletions
@@ -7,6 +7,7 @@ import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
import { Input, Popup } from '../../lib/custom-ui'; import { Input, Popup } from '../../lib/custom-ui';
import { useForm } from '../../hooks'; import { useForm } from '../../hooks';
import { isPassword } from '../../utils/validator';
import styles from './UserPasswordEditStep.module.scss'; import styles from './UserPasswordEditStep.module.scss';
@@ -56,7 +57,7 @@ const UserPasswordEditStep = React.memo(
const currentPasswordField = useRef(null); const currentPasswordField = useRef(null);
const handleSubmit = useCallback(() => { const handleSubmit = useCallback(() => {
if (!data.password) { if (!data.password || !isPassword(data.password)) {
passwordField.current.select(); passwordField.current.select();
return; return;
} }
@@ -112,14 +113,18 @@ const UserPasswordEditStep = React.memo(
)} )}
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<div className={styles.text}>{t('common.newPassword')}</div> <div className={styles.text}>{t('common.newPassword')}</div>
<Input.Password <div className={styles.field}>
fluid <Input.Password
ref={passwordField} fluid
name="password" ref={passwordField}
value={data.password} name="password"
className={styles.field} value={data.password}
onChange={handleFieldChange} onChange={handleFieldChange}
/> />
<div className={styles.note}>
{t('common.mustBeAtLeast6CharactersLongAndContainAtLeastOneLetterAndNumber')}
</div>
</div>
{usePasswordConfirmation && ( {usePasswordConfirmation && (
<> <>
<div className={styles.text}>{t('common.currentPassword')}</div> <div className={styles.text}>{t('common.currentPassword')}</div>
@@ -3,6 +3,12 @@
margin-bottom: 8px; margin-bottom: 8px;
} }
.note {
font-size: 11px;
margin-top: 4px;
opacity: 0.5;
}
.text { .text {
color: #444444; color: #444444;
font-size: 12px; font-size: 12px;
@@ -1,6 +1,7 @@
:global(#app) { :global(#app) {
.action { .action {
border: none; border: none;
border-radius: 0.28571429rem;
display: inline-block; display: inline-block;
height: 36px; height: 36px;
overflow: hidden; overflow: hidden;
+1 -1
View File
@@ -117,7 +117,7 @@ export default {
phone: 'Telefon', phone: 'Telefon',
preferences: 'Volby', preferences: 'Volby',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: dejte Ctrl-V (Cmd-V na Mac) pro vložení přílohy ze schránky.', 'Tip: dejte Ctrl-V (Cmd-V na Mac) pro vložení přílohy ze schránky',
project: 'Projekt', project: 'Projekt',
projectNotFound_title: 'Projekt nenalezen', projectNotFound_title: 'Projekt nenalezen',
removeManager_title: 'Odstranit vedoucího', removeManager_title: 'Odstranit vedoucího',
+2 -2
View File
@@ -23,7 +23,7 @@ export default {
administrator: 'Administrator', administrator: 'Administrator',
all: 'Alle', all: 'Alle',
allChangesWillBeAutomaticallySavedAfterConnectionRestored: allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'Alle Änderungen werden automatisch gespeichert,<br />sobald die Verbindung wiederhergestellt wurde.', 'Alle Änderungen werden automatisch gespeichert,<br />sobald die Verbindung wiederhergestellt wurde',
areYouSureYouWantToDeleteThisAttachment: areYouSureYouWantToDeleteThisAttachment:
'Sind Sie sicher, dass Sie diesen Anhang löschen möchten?', 'Sind Sie sicher, dass Sie diesen Anhang löschen möchten?',
areYouSureYouWantToDeleteThisBoard: 'Sind Sie sicher, dass Sie dieses Board löschen möchten?', areYouSureYouWantToDeleteThisBoard: 'Sind Sie sicher, dass Sie dieses Board löschen möchten?',
@@ -123,7 +123,7 @@ export default {
phone: 'Telefon', phone: 'Telefon',
preferences: 'Voreinstellungen', preferences: 'Voreinstellungen',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Tipp: Drücken Sie STRG-V (Cmd-V auf Mac), um einen Anhang aus der Zwischenablage hinzuzufügen.', 'Tipp: Drücken Sie STRG-V (Cmd-V auf Mac), um einen Anhang aus der Zwischenablage hinzuzufügen',
project: 'Projekt', project: 'Projekt',
projectNotFound_title: 'Projekt nicht gefunden', projectNotFound_title: 'Projekt nicht gefunden',
removeManager_title: 'Manager entfernen', removeManager_title: 'Manager entfernen',
+3 -1
View File
@@ -105,6 +105,8 @@ export default {
members: 'Members', members: 'Members',
minutes: 'Minutes', minutes: 'Minutes',
moveCard_title: 'Move Card', moveCard_title: 'Move Card',
mustBeAtLeast6CharactersLongAndContainAtLeastOneLetterAndNumber:
'Must be at least 6 characters long and contain at least one letter and number',
name: 'Name', name: 'Name',
newEmail: 'New e-mail', newEmail: 'New e-mail',
newPassword: 'New password', newPassword: 'New password',
@@ -121,7 +123,7 @@ export default {
phone: 'Phone', phone: 'Phone',
preferences: 'Preferences', preferences: 'Preferences',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: press Ctrl-V (Cmd-V on Mac) to add an attachment from the clipboard.', 'Tip: press Ctrl-V (Cmd-V on Mac) to add an attachment from the clipboard',
project: 'Project', project: 'Project',
projectNotFound_title: 'Project Not Found', projectNotFound_title: 'Project Not Found',
removeManager_title: 'Remove Manager', removeManager_title: 'Remove Manager',
+2 -2
View File
@@ -106,7 +106,7 @@ export default {
phone: 'Teléfono', phone: 'Teléfono',
preferences: 'Preferencias', preferences: 'Preferencias',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: presiona Ctrl-V (Cmd-V en Mac) para añadir adjuntos desde el portapapeles.', 'Tip: presiona Ctrl-V (Cmd-V en Mac) para añadir adjuntos desde el portapapeles',
project: 'Proyecto', project: 'Proyecto',
projectNotFound_title: 'Proyecto no encontrado', projectNotFound_title: 'Proyecto no encontrado',
removeMember_title: 'Remover Miembro', removeMember_title: 'Remover Miembro',
@@ -115,7 +115,7 @@ export default {
selectList: 'Seleccionar Lista', selectList: 'Seleccionar Lista',
selectProject: 'Seleccionar Proyecto', selectProject: 'Seleccionar Proyecto',
settings: 'Configuraciones', settings: 'Configuraciones',
subscribeToMyOwnCardsByDefault: 'Suscribirme a mis propias tarjetas por defecto.', subscribeToMyOwnCardsByDefault: 'Suscribirme a mis propias tarjetas por defecto',
taskActions_title: 'Acciones de Tareas', taskActions_title: 'Acciones de Tareas',
tasks: 'Tareas', tasks: 'Tareas',
time: 'Tiempo', time: 'Tiempo',
+1 -1
View File
@@ -121,7 +121,7 @@ export default {
phone: 'Telefono', phone: 'Telefono',
preferences: 'Preferenze', preferences: 'Preferenze',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: prmi Ctrl-V (Cmd-V on Mac) per aggiungere un allegato dalla clipboard.', 'Tip: prmi Ctrl-V (Cmd-V on Mac) per aggiungere un allegato dalla clipboard',
project: 'Progetto', project: 'Progetto',
projectNotFound_title: 'Progetto non trovato', projectNotFound_title: 'Progetto non trovato',
removeManager_title: 'Rimuovi Manager', removeManager_title: 'Rimuovi Manager',
+1 -1
View File
@@ -117,7 +117,7 @@ export default {
phone: '電話番号', phone: '電話番号',
preferences: '環境設定', preferences: '環境設定',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'ヒント: Ctrl-V(MacではCmd-V)を押して、クリップボードから添付ファイルを追加します。', 'ヒント: Ctrl-V(MacではCmd-V)を押して、クリップボードから添付ファイルを追加します',
project: 'プロジェクト', project: 'プロジェクト',
projectNotFound_title: 'プロジェクトがありません', projectNotFound_title: 'プロジェクトがありません',
removeManager_title: 'マネージャーを削除', removeManager_title: 'マネージャーを削除',
+1 -1
View File
@@ -106,7 +106,7 @@ export default {
phone: 'Telefon', phone: 'Telefon',
preferences: 'Preferencje', preferences: 'Preferencje',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Podpowiedź: naciśnij Ctrl-V (Cmd-V na Macu) aby dodać załącznik ze schowka.', 'Podpowiedź: naciśnij Ctrl-V (Cmd-V na Macu) aby dodać załącznik ze schowka',
project: 'Projekt', project: 'Projekt',
projectNotFound_title: 'Projekt Nie Znaleziony', projectNotFound_title: 'Projekt Nie Znaleziony',
removeMember_title: 'Usuń Członka', removeMember_title: 'Usuń Członka',
+3 -1
View File
@@ -100,6 +100,8 @@ export default {
members: 'Участники', members: 'Участники',
minutes: 'Минуты', minutes: 'Минуты',
moveCard: 'Перемещение карточки', moveCard: 'Перемещение карточки',
mustBeAtLeast6CharactersLongAndContainAtLeastOneLetterAndNumber:
'Должен быть не менее 6 символов и содержать хотя бы одну букву и цифру',
name: 'Имя', name: 'Имя',
newEmail: 'Новый e-mail', newEmail: 'Новый e-mail',
newPassword: 'Новый пароль', newPassword: 'Новый пароль',
@@ -116,7 +118,7 @@ export default {
phone: 'Телефон', phone: 'Телефон',
preferences: 'Предпочтения', preferences: 'Предпочтения',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Совет: нажмите Ctrl-V (Cmd-V на Mac), чтобы добавить вложение из буфера обмена.', 'Совет: нажмите Ctrl-V (Cmd-V на Mac), чтобы добавить вложение из буфера обмена',
project: 'Проект', project: 'Проект',
projectActions: 'Действия с проектом', projectActions: 'Действия с проектом',
projectNotFound: 'Проект не найден', projectNotFound: 'Проект не найден',
+1 -1
View File
@@ -118,7 +118,7 @@ export default {
phone: 'Telefón', phone: 'Telefón',
preferences: 'Voľby', preferences: 'Voľby',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: stlačte Ctrl-V (Cmd-V na Mac) pre vloženie prílohy zo schránky.', 'Tip: stlačte Ctrl-V (Cmd-V na Mac) pre vloženie prílohy zo schránky',
project: 'Projekt', project: 'Projekt',
projectNotFound_title: 'Projekt neexistuje', projectNotFound_title: 'Projekt neexistuje',
removeManager_title: 'Odstrániť správcu', removeManager_title: 'Odstrániť správcu',
+1 -1
View File
@@ -119,7 +119,7 @@ export default {
phone: 'Telefon', phone: 'Telefon',
preferences: 'Preferenser', preferences: 'Preferenser',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Tips: tryck på Ctrl-V (Cmd-V på Mac) för att lägga till en bilaga från urklipp.', 'Tips: tryck på Ctrl-V (Cmd-V på Mac) för att lägga till en bilaga från urklipp',
project: 'Projekt', project: 'Projekt',
projectNotFound_title: 'Projekt Ej Funnet', projectNotFound_title: 'Projekt Ej Funnet',
removeManager_title: 'Ta Bort Projektledare', removeManager_title: 'Ta Bort Projektledare',
+1 -1
View File
@@ -112,7 +112,7 @@ export default {
phone: 'Telefon', phone: 'Telefon',
preferences: 'Afzalliklar', preferences: 'Afzalliklar',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
"Tip: Buferdan ilova qo'shish uchun Ctrl-V (Mac da Cmd-V) ni bosing.", "Tip: Buferdan ilova qo'shish uchun Ctrl-V (Mac da Cmd-V) ni bosing",
project: 'Loyiha', project: 'Loyiha',
projectNotFound_title: 'Loyiha Topilmadi', projectNotFound_title: 'Loyiha Topilmadi',
removeManager_title: "Boshqaruvchini O'chirish", removeManager_title: "Boshqaruvchini O'chirish",
+1 -1
View File
@@ -110,7 +110,7 @@ export default {
phone: '电话', phone: '电话',
preferences: '偏好', preferences: '偏好',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'提示: 点击 Ctrl-V (Mac: Cmd-V) 从剪切板添加附件.', '提示: 点击 Ctrl-V (Mac: Cmd-V) 从剪切板添加附件',
project: '项目', project: '项目',
projectNotFound_title: '项目未找到', projectNotFound_title: '项目未找到',
removeManager_title: '删除管理员', removeManager_title: '删除管理员',
+5 -1
View File
@@ -1,6 +1,10 @@
const PASSWORD_REGEX = /^(?=.*[A-Za-z])(?=.*\d).+$/;
const USERNAME_REGEX = /^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$/; const USERNAME_REGEX = /^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$/;
// eslint-disable-next-line import/prefer-default-export export const isPassword = (string) => {
return string.length >= 3 && PASSWORD_REGEX.test(string);
};
export const isUsername = (string) => { export const isUsername = (string) => {
return string.length >= 3 && string.length <= 16 && USERNAME_REGEX.test(string); return string.length >= 3 && string.length <= 16 && USERNAME_REGEX.test(string);
}; };
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "planka", "name": "planka",
"version": "1.7.1", "version": "1.7.3",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "planka", "name": "planka",
"version": "1.7.1", "version": "1.7.3",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "planka", "name": "planka",
"version": "1.7.1", "version": "1.7.3",
"private": true, "private": true,
"homepage": "https://plankanban.github.io/planka", "homepage": "https://plankanban.github.io/planka",
"repository": { "repository": {
+39 -26
View File
@@ -1,3 +1,6 @@
const util = require('util');
const { v4: uuid } = require('uuid');
const Errors = { const Errors = {
NOT_ENOUGH_RIGHTS: { NOT_ENOUGH_RIGHTS: {
notEnoughRights: 'Not enough rights', notEnoughRights: 'Not enough rights',
@@ -5,6 +8,9 @@ const Errors = {
CARD_NOT_FOUND: { CARD_NOT_FOUND: {
cardNotFound: 'Card not found', cardNotFound: 'Card not found',
}, },
NO_FILE_WAS_UPLOADED: {
noFileWasUploaded: 'No file was uploaded',
},
}; };
module.exports = { module.exports = {
@@ -27,6 +33,9 @@ module.exports = {
cardNotFound: { cardNotFound: {
responseType: 'notFound', responseType: 'notFound',
}, },
noFileWasUploaded: {
responseType: 'unprocessableEntity',
},
uploadError: { uploadError: {
responseType: 'unprocessableEntity', responseType: 'unprocessableEntity',
}, },
@@ -52,33 +61,37 @@ module.exports = {
throw Errors.NOT_ENOUGH_RIGHTS; throw Errors.NOT_ENOUGH_RIGHTS;
} }
this.req const upload = util.promisify((options, callback) =>
.file('file') this.req.file('file').upload(options, (error, files) => callback(error, files)),
.upload(sails.helpers.utils.createAttachmentReceiver(), async (error, files) => { );
if (error) {
return exits.uploadError(error.message);
}
if (files.length === 0) { let files;
return exits.uploadError('No file was uploaded'); try {
} files = await upload({
saveAs: uuid(),
const file = files[0]; maxBytes: null,
const attachment = await sails.helpers.attachments.createOne(
{
...file.extra,
filename: file.filename,
},
currentUser,
card,
inputs.requestId,
this.req,
);
return exits.success({
item: attachment.toJSON(),
});
}); });
} catch (error) {
return exits.uploadError(error.message); // TODO: add error
}
if (files.length === 0) {
throw Errors.NO_FILE_WAS_UPLOADED;
}
const file = _.last(files);
const fileData = await sails.helpers.attachments.processUploadedFile(file);
const attachment = await sails.helpers.attachments.createOne(
fileData,
currentUser,
card,
inputs.requestId,
this.req,
);
return exits.success({
item: attachment,
});
}, },
}; };
+3 -3
View File
@@ -23,7 +23,7 @@ module.exports = {
}, },
}, },
async fn(inputs, exits) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { board } = await sails.helpers.boards const { board } = await sails.helpers.boards
@@ -61,7 +61,7 @@ module.exports = {
const tasks = await sails.helpers.cards.getTasks(cardIds); const tasks = await sails.helpers.cards.getTasks(cardIds);
const attachments = await sails.helpers.cards.getAttachments(cardIds); const attachments = await sails.helpers.cards.getAttachments(cardIds);
return exits.success({ return {
items: cards, items: cards,
included: { included: {
cardMemberships, cardMemberships,
@@ -69,6 +69,6 @@ module.exports = {
tasks, tasks,
attachments, attachments,
}, },
}); };
}, },
}; };
@@ -1,7 +1,17 @@
const util = require('util');
const rimraf = require('rimraf');
const { v4: uuid } = require('uuid');
const Errors = { const Errors = {
PROJECT_NOT_FOUND: { PROJECT_NOT_FOUND: {
projectNotFound: 'Project not found', projectNotFound: 'Project not found',
}, },
NO_FILE_WAS_UPLOADED: {
noFileWasUploaded: 'No file was uploaded',
},
FILE_IS_NOT_IMAGE: {
fileIsNotImage: 'File is not image',
},
}; };
module.exports = { module.exports = {
@@ -17,6 +27,12 @@ module.exports = {
projectNotFound: { projectNotFound: {
responseType: 'notFound', responseType: 'notFound',
}, },
noFileWasUploaded: {
responseType: 'unprocessableEntity',
},
fileIsNotImage: {
responseType: 'unprocessableEntity',
},
uploadError: { uploadError: {
responseType: 'unprocessableEntity', responseType: 'unprocessableEntity',
}, },
@@ -37,32 +53,52 @@ module.exports = {
throw Errors.PROJECT_NOT_FOUND; // Forbidden throw Errors.PROJECT_NOT_FOUND; // Forbidden
} }
this.req const upload = util.promisify((options, callback) =>
.file('file') this.req.file('file').upload(options, (error, files) => callback(error, files)),
.upload(sails.helpers.utils.createProjectBackgroundImageReceiver(), async (error, files) => { );
if (error) {
return exits.uploadError(error.message);
}
if (files.length === 0) { let files;
return exits.uploadError('No file was uploaded'); try {
} files = await upload({
saveAs: uuid(),
project = await sails.helpers.projects.updateOne( maxBytes: null,
project,
{
backgroundImageDirname: files[0].extra.dirname,
},
this.req,
);
if (!project) {
throw Errors.PROJECT_NOT_FOUND;
}
return exits.success({
item: project.toJSON(),
});
}); });
} catch (error) {
return exits.uploadError(error.message); // TODO: add error
}
if (files.length === 0) {
throw Errors.NO_FILE_WAS_UPLOADED;
}
const file = _.last(files);
const fileData = await sails.helpers.projects
.processUploadedBackgroundImageFile(file)
.intercept('fileIsNotImage', () => {
try {
rimraf.sync(file.fd);
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
return Errors.FILE_IS_NOT_IMAGE;
});
project = await sails.helpers.projects.updateOne(
project,
{
backgroundImageDirname: fileData.dirname,
},
this.req,
);
if (!project) {
throw Errors.PROJECT_NOT_FOUND;
}
return exits.success({
item: project,
});
}, },
}; };
+2
View File
@@ -16,6 +16,8 @@ module.exports = {
}, },
password: { password: {
type: 'string', type: 'string',
minLength: 6,
regex: /^(?=.*[A-Za-z])(?=.*\d).+$/,
required: true, required: true,
}, },
name: { name: {
+62 -26
View File
@@ -1,7 +1,17 @@
const util = require('util');
const rimraf = require('rimraf');
const { v4: uuid } = require('uuid');
const Errors = { const Errors = {
USER_NOT_FOUND: { USER_NOT_FOUND: {
userNotFound: 'User not found', userNotFound: 'User not found',
}, },
NO_FILE_WAS_UPLOADED: {
noFileWasUploaded: 'No file was uploaded',
},
FILE_IS_NOT_IMAGE: {
fileIsNotImage: 'File is not image',
},
}; };
module.exports = { module.exports = {
@@ -17,6 +27,12 @@ module.exports = {
userNotFound: { userNotFound: {
responseType: 'notFound', responseType: 'notFound',
}, },
noFileWasUploaded: {
responseType: 'unprocessableEntity',
},
fileIsNotImage: {
responseType: 'unprocessableEntity',
},
uploadError: { uploadError: {
responseType: 'unprocessableEntity', responseType: 'unprocessableEntity',
}, },
@@ -38,33 +54,53 @@ module.exports = {
user = currentUser; user = currentUser;
} }
this.req const upload = util.promisify((options, callback) =>
.file('file') this.req.file('file').upload(options, (error, files) => callback(error, files)),
.upload(sails.helpers.utils.createUserAvatarReceiver(), async (error, files) => { );
if (error) {
return exits.uploadError(error.message);
}
if (files.length === 0) { let files;
return exits.uploadError('No file was uploaded'); try {
} files = await upload({
saveAs: uuid(),
user = await sails.helpers.users.updateOne( maxBytes: null,
user,
{
avatarDirname: files[0].extra.dirname,
},
currentUser,
this.req,
);
if (!user) {
throw Errors.USER_NOT_FOUND;
}
return exits.success({
item: user.toJSON(),
});
}); });
} catch (error) {
return exits.uploadError(error.message); // TODO: add error
}
if (files.length === 0) {
throw Errors.NO_FILE_WAS_UPLOADED;
}
const file = _.last(files);
const fileData = await sails.helpers.users
.processUploadedAvatarFile(file)
.intercept('fileIsNotImage', () => {
try {
rimraf.sync(file.fd);
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
return Errors.FILE_IS_NOT_IMAGE;
});
user = await sails.helpers.users.updateOne(
user,
{
avatarDirname: fileData.dirname,
},
currentUser,
this.req,
);
if (!user) {
throw Errors.USER_NOT_FOUND;
}
return exits.success({
item: user,
});
}, },
}; };
@@ -18,6 +18,8 @@ module.exports = {
}, },
password: { password: {
type: 'string', type: 'string',
minLength: 6,
regex: /^(?=.*[A-Za-z])(?=.*\d).+$/,
required: true, required: true,
}, },
currentPassword: { currentPassword: {
@@ -0,0 +1,77 @@
const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
const moveFile = require('move-file');
const filenamify = require('filenamify');
const { v4: uuid } = require('uuid');
const sharp = require('sharp');
module.exports = {
inputs: {
file: {
type: 'json',
required: true,
},
},
async fn(inputs) {
const dirname = uuid();
// FIXME: https://github.com/sindresorhus/filenamify/issues/13
const filename = filenamify(inputs.file.filename);
const rootPath = path.join(sails.config.custom.attachmentsPath, dirname);
const filePath = path.join(rootPath, filename);
fs.mkdirSync(rootPath);
await moveFile(inputs.file.fd, filePath);
const image = sharp(filePath);
let metadata;
try {
metadata = await image.metadata();
} catch (error) {} // eslint-disable-line no-empty
const fileData = {
dirname,
filename,
image: null,
name: inputs.file.filename,
};
if (metadata && !['svg', 'pdf'].includes(metadata.format)) {
const thumbnailsPath = path.join(rootPath, 'thumbnails');
fs.mkdirSync(thumbnailsPath);
try {
await image
.resize(
metadata.height > metadata.width
? {
width: 256,
height: 320,
}
: {
width: 256,
},
)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toFile(path.join(thumbnailsPath, 'cover-256.jpg'));
fileData.image = _.pick(metadata, ['width', 'height']);
} catch (error1) {
try {
rimraf.sync(thumbnailsPath);
} catch (error2) {
console.warn(error2.stack); // eslint-disable-line no-console
}
}
}
return fileData;
},
};
@@ -0,0 +1,63 @@
const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
const { v4: uuid } = require('uuid');
const sharp = require('sharp');
module.exports = {
inputs: {
file: {
type: 'json',
required: true,
},
},
exits: {
fileIsNotImage: {},
},
async fn(inputs) {
const image = sharp(inputs.file.fd);
try {
await image.metadata();
} catch (error) {
throw 'fileIsNotImage';
}
const dirname = uuid();
const rootPath = path.join(sails.config.custom.projectBackgroundImagesPath, dirname);
fs.mkdirSync(rootPath);
try {
await image.jpeg().toFile(path.join(rootPath, 'original.jpg'));
await image
.resize(336, 200)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toFile(path.join(rootPath, 'cover-336.jpg'));
} catch (error1) {
try {
rimraf.sync(rootPath);
} 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,
};
},
};
@@ -0,0 +1,68 @@
const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
const { v4: uuid } = require('uuid');
const sharp = require('sharp');
module.exports = {
inputs: {
file: {
type: 'json',
required: true,
},
},
exits: {
fileIsNotImage: {},
},
async fn(inputs) {
const image = sharp(inputs.file.fd);
try {
await image.metadata();
} catch (error) {
throw 'fileIsNotImage';
}
const dirname = uuid();
const rootPath = path.join(sails.config.custom.userAvatarsPath, dirname);
fs.mkdirSync(rootPath);
try {
await image
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toFile(path.join(rootPath, 'original.jpg'));
await image
.resize(100, 100)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toFile(path.join(rootPath, 'square-100.jpg'));
} catch (error1) {
try {
rimraf.sync(rootPath);
} 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,
};
},
};
@@ -1,102 +0,0 @@
const fs = require('fs');
const path = require('path');
const util = require('util');
const stream = require('stream');
const streamToArray = require('stream-to-array');
const filenamify = require('filenamify');
const { v4: uuid } = require('uuid');
const sharp = require('sharp');
const writeFile = util.promisify(fs.writeFile);
module.exports = {
sync: true,
fn() {
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) => (Buffer.isBuffer(part) ? part : Buffer.from(part)))),
);
try {
const dirname = uuid();
// FIXME: https://github.com/sindresorhus/filenamify/issues/13
const filename = filenamify(file.filename);
const rootPath = path.join(sails.config.custom.attachmentsPath, dirname);
fs.mkdirSync(rootPath);
await writeFile(path.join(rootPath, filename), buffer);
const image = sharp(buffer);
let metadata;
try {
metadata = await image.metadata();
} catch (error) {} // eslint-disable-line no-empty
const extra = {
dirname,
name: file.filename,
};
if (metadata && !['svg', 'pdf'].includes(metadata.format)) {
let cover256Buffer;
if (metadata.height > metadata.width) {
cover256Buffer = await image
.resize(256, 320)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toBuffer();
} else {
cover256Buffer = await image
.resize({
width: 256,
})
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toBuffer();
}
const thumbnailsPath = path.join(rootPath, 'thumbnails');
fs.mkdirSync(thumbnailsPath);
await writeFile(path.join(thumbnailsPath, 'cover-256.jpg'), cover256Buffer);
extra.image = _.pick(metadata, ['width', 'height']);
} else {
extra.image = null;
}
Object.assign(file, {
filename,
extra,
});
return done();
} catch (error) {
return done(error);
}
};
return receiver;
},
};
@@ -1,65 +0,0 @@
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() {
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) => (Buffer.isBuffer(part) ? part : Buffer.from(part)))),
);
try {
const originalBuffer = await sharp(buffer).jpeg().toBuffer();
const cover336Buffer = await sharp(buffer)
.resize(336, 200)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.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 receiver;
},
};
@@ -1,70 +0,0 @@
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() {
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) => (Buffer.isBuffer(part) ? part : Buffer.from(part)))),
);
try {
const originalBuffer = await sharp(buffer)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toBuffer();
const square100Buffer = await sharp(buffer)
.resize(100, 100)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toBuffer();
const dirname = uuid();
const rootPath = path.join(sails.config.custom.userAvatarsPath, dirname);
fs.mkdirSync(rootPath);
await writeFile(path.join(rootPath, 'original.jpg'), originalBuffer);
await writeFile(path.join(rootPath, 'square-100.jpg'), square100Buffer);
// eslint-disable-next-line no-param-reassign
file.extra = {
dirname,
};
return done();
} catch (error) {
return done(error);
}
};
return receiver;
},
};
+38
View File
@@ -14,6 +14,7 @@
"knex": "^2.2.0", "knex": "^2.2.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.29.4",
"move-file": "^2.1.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sails": "^1.5.2", "sails": "^1.5.2",
"sails-hook-orm": "^4.0.1", "sails-hook-orm": "^4.0.1",
@@ -4398,6 +4399,28 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/move-file": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/move-file/-/move-file-2.1.0.tgz",
"integrity": "sha512-i9qLW6gqboJ5Ht8bauZi7KlTnQ3QFpBCvMvFfEcHADKgHGeJ9BZMO7SFCTwHPV9Qa0du9DYY1Yx3oqlGt30nXA==",
"dependencies": {
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=10.17"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/move-file/node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"engines": {
"node": ">=8"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -11370,6 +11393,21 @@
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
}, },
"move-file": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/move-file/-/move-file-2.1.0.tgz",
"integrity": "sha512-i9qLW6gqboJ5Ht8bauZi7KlTnQ3QFpBCvMvFfEcHADKgHGeJ9BZMO7SFCTwHPV9Qa0du9DYY1Yx3oqlGt30nXA==",
"requires": {
"path-exists": "^4.0.0"
},
"dependencies": {
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
}
}
},
"ms": { "ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+1
View File
@@ -46,6 +46,7 @@
"knex": "^2.2.0", "knex": "^2.2.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.29.4",
"move-file": "^2.1.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sails": "^1.5.2", "sails": "^1.5.2",
"sails-hook-orm": "^4.0.1", "sails-hook-orm": "^4.0.1",