Compare commits

..
30 Commits
Author SHA1 Message Date
Maksim Eltyshev 8d74cc1732 chore: Update version 2024-07-15 16:24:31 +02:00
Emmanuel Guyot ea94fe8bf3 fix: Order users by name (#806) 2024-07-15 15:26:26 +02:00
Αλέξανδρος 6052f8999f fix: Fix application crashing when webhook is unavailable (#815) 2024-07-15 14:40:33 +02:00
ItzAndriss 67d1b1c194 fix: Update Hungarian translation (#823) 2024-07-15 14:25:31 +02:00
Stephan Michard b3cb8405f5 fix: Update German translation (#821) 2024-07-15 14:19:06 +02:00
Maksim Eltyshev 7acaec6d04 fix: Fix incorrect related data when transferring card
Closes #431, closes #803
2024-07-15 14:01:06 +02:00
Maksim Eltyshev 72896a66b3 chore: Update version 2024-06-21 12:11:15 +02:00
Maksim Eltyshev 4be4783251 fix: Fix icon spacing for action buttons 2024-06-21 12:01:46 +02:00
HannesOberreiter 5c6c6a0bcf feat: Add copy link action to card modal (#804) 2024-06-21 11:48:36 +02:00
HannesOberreiter ba178d66fa docs: Add types to webhook function (#796) 2024-06-18 15:41:12 +02:00
HannesOberreiter 270ce29f87 feat: Add custom user agent with base url to webhook (#799) 2024-06-18 15:36:51 +02:00
HannesOberreiter de1ed3c784 ref: Define all possible event types in webhook module (#795) 2024-06-18 15:32:39 +02:00
Maksim Eltyshev caf8790f28 chore: Update version 2024-06-14 17:18:42 +02:00
Sarah Soo 81436b373d feat: Ability to specify existing secrets for values in Helm (#791)
Closes #790
2024-06-14 16:41:10 +02:00
NathanVss cc1e886a31 feat: Ability to allow everyone to create projects (#787) 2024-06-14 16:38:06 +02:00
Maksim Eltyshev f0e9fe7904 chore: Update version 2024-06-12 09:06:20 +02:00
Gavin Mogan 5fcea5d651 fix: Handle WEBHOOKS env variable being unset (#785)
Closes #784
2024-06-12 08:59:36 +02:00
Maksim Eltyshev a963d85a66 chore: Update version 2024-06-12 01:02:55 +02:00
Samuel 04b42decef meta: Add issue templates (#782) 2024-06-12 00:59:37 +02:00
Maksim Eltyshev c065566c15 feat: Webhooks configuration, all events support, refactoring 2024-06-12 00:51:36 +02:00
HannesOberreiter 3779bdb053 feat: Events via webhook (#771)
Closes #215, closes #656
2024-06-06 20:22:14 +02:00
HannesOberreiter 4124ab17d2 fix: Focus end of field when editing (#779) 2024-06-06 20:06:16 +02:00
Maksim Eltyshev 0e3bc92a61 chore: Update version 2024-06-02 01:36:29 +02:00
Maksim Eltyshev b8d7e713b9 chore: Update dependencies 2024-06-02 01:34:03 +02:00
Emmanuel Guyot 017eee8302 fix: Use server base path for socket connection (#772)
Closes #768
2024-05-23 23:59:44 +02:00
Mitch Harvey 1b28892cf5 fix: Fix Helm template for dburl secret (#770) 2024-05-21 23:32:07 +02:00
Maksim Eltyshev 013ea1b869 chore: Update version 2024-05-21 13:58:28 +02:00
Blyamur 4975fa2eeb fix: Update Russian translation (#767) 2024-05-21 13:51:57 +02:00
Blyamur 3e28ad87c7 fix: Update Russian translation (#766) 2024-05-21 13:51:34 +02:00
Smiley3112 2d6666d693 feat: Add SMTP_NAME environment variable (#761)
Closes #758
2024-05-18 15:02:21 +02:00
134 changed files with 3083 additions and 2123 deletions
+53
View File
@@ -0,0 +1,53 @@
name: "🐛 Bug Report"
description: Report a bug found while using Planka
title: "[Bug]: "
labels: ["Type: Bug", "Status: Triage"]
body:
- type: dropdown
id: issue-type
attributes:
label: Where is the problem occurring?
description: Select the part of the application where you encountered the issue.
options:
- "I encountered the problem while using the application (Frontend)"
- "I encountered the problem while interacting with the server (Backend)"
- "I'm not sure"
- type: dropdown
id: browsers
attributes:
label: What browsers are you seeing the problem on?
multiple: true
options:
- Brave
- Chrome
- Firefox
- Microsoft Edge
- Safari
- Other
- type: textarea
id: current-behavior
attributes:
label: Current behaviour
description: A description of what is currently happening, including screenshots and other useful information (**DO NOT INCLUDE PRIVATE INFORMATION**).
placeholder: Currently...
validations:
required: true
- type: textarea
id: desired-behavior
attributes:
label: Desired behaviour
description: A clear description of what you think should happen.
placeholder: In this situation, I expected ...
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: Clearly describe which steps or actions you have taken to arrive at the problem. If you have some experience with the code, please link to the specific pieces of code.
placeholder: I did X, then Y, before arriving at Z, when ERROR ...
validations:
required: true
- type: textarea
id: other
attributes:
label: Other information
description: Any other details?
@@ -0,0 +1,33 @@
name: "✨ Feature Request"
description: Suggest a feature or enhancement to improve Planka.
labels: ["Type: Idea"]
body:
- type: dropdown
id: idea-type
attributes:
label: Is this a feature for the backend or frontend?
multiple: true
options:
- Backend
- Frontend
validations:
required: true
- type: textarea
id: feature
attributes:
label: What would you like?
description: A clear description of the feature or enhancement wanted.
placeholder: I'd like to be able to...
validations:
required: true
- type: textarea
id: reason
attributes:
label: Why is this needed?
description: A clear description of why this would be useful to have.
placeholder: I want this because...
- type: textarea
id: other
attributes:
label: Other information
placeholder: Any other details?
Executable → Regular
-3
View File
@@ -1,4 +1 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
+2 -2
View File
@@ -15,13 +15,13 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.30
version: 0.2.3
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.17.3"
appVersion: "1.19.2"
dependencies:
- alias: postgresql
+23 -3
View File
@@ -68,7 +68,7 @@ spec:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: .Values.existingDburlSecret
name: {{ .Values.existingDburlSecret }}
key: uri
{{- else }}
- name: DATABASE_URL
@@ -90,17 +90,37 @@ spec:
value: http://localhost:3000
{{- end }}
- name: SECRET_KEY
{{- if .Values.existingSecretkeySecret }}
valueFrom:
secretKeyRef:
name: {{ .Values.existingSecretkeySecret }}
key: key
{{- else }}
value: {{ required "A secret key needs to be generated using 'openssl rand -hex 64' and assigned to secretkey." .Values.secretkey }}
{{- end }}
- name: TRUST_PROXY
value: "0"
- name: DEFAULT_ADMIN_EMAIL
value: {{ .Values.admin_email }}
- name: DEFAULT_ADMIN_PASSWORD
value: {{ .Values.admin_password }}
- name: DEFAULT_ADMIN_NAME
value: {{ .Values.admin_name }}
{{- if .Values.existingAdminCredsSecret }}
- name: DEFAULT_ADMIN_USERNAME
valueFrom:
secretKeyRef:
name: {{ .Values.existingAdminCredsSecret }}
key: username
- name: DEFAULT_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.existingAdminCredsSecret }}
key: password
{{- else }}
- name: DEFAULT_ADMIN_USERNAME
value: {{ .Values.admin_username }}
- name: DEFAULT_ADMIN_PASSWORD
value: {{ .Values.admin_password }}
{{- end }}
{{ range $k, $v := .Values.env }}
- name: {{ $k | quote }}
value: {{ $v | quote }}
+10
View File
@@ -17,6 +17,16 @@ fullnameOverride: ""
# Generate a secret using openssl rand -base64 45
secretkey: ""
## @param existingSecretkeySecret Name of an existing secret containing the session key string
## NOTE: Must contain key `key`
## NOTE: When it's set, the secretkey parameter is ignored
existingSecretkeySecret: ""
## @param existingAdminCredsSecret Name of an existing secret containing the admin username and password
## NOTE: Must contain keys `username` and `password`
## NOTE: When it's set, the `admin_username` and `admin_password` parameters are ignored
existingAdminCredsSecret: ""
# Base url for Planka. Will override `ingress.hosts[0].host`
# Defaults to `http://localhost:3000` if ingress is disabled.
baseUrl: ""
+1 -1
View File
@@ -1 +1 @@
REACT_APP_VERSION=1.17.3
REACT_APP_VERSION=1.19.2
+1037 -1649
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -65,8 +65,8 @@
"dequal": "^2.0.3",
"easymde": "^2.18.0",
"history": "^5.3.0",
"i18next": "^23.11.2",
"i18next-browser-languagedetector": "^7.2.1",
"i18next": "^23.11.5",
"i18next-browser-languagedetector": "^8.0.0",
"initials": "^3.1.2",
"js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0",
@@ -75,7 +75,7 @@
"lodash": "^4.17.21",
"nanoid": "^5.0.7",
"node-sass": "^9.0.0",
"photoswipe": "^5.4.3",
"photoswipe": "^5.4.4",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-app-rewired": "^2.2.1",
@@ -83,12 +83,12 @@
"react-datepicker": "^4.25.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-i18next": "^13.5.0",
"react-i18next": "^14.1.2",
"react-input-mask": "^2.0.4",
"react-markdown": "^8.0.7",
"react-photoswipe-gallery": "^2.2.7",
"react-redux": "^8.1.3",
"react-router-dom": "^6.22.3",
"react-router-dom": "^6.23.1",
"react-scripts": "5.0.1",
"react-simplemde-editor": "^5.2.0",
"react-textarea-autosize": "^8.5.3",
@@ -102,13 +102,13 @@
"sails.io.js": "^1.2.1",
"semantic-ui-react": "^2.1.5",
"socket.io-client": "^2.5.0",
"validator": "^13.11.0",
"validator": "^13.12.0",
"whatwg-fetch": "^3.6.20",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.3.1",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2",
"babel-preset-airbnb": "^5.0.0",
"chai": "^4.4.1",
@@ -116,8 +116,8 @@
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2",
"react-test-renderer": "^18.2.0"
}
}
+6 -1
View File
@@ -57,10 +57,15 @@ updateCard.failure = (id, error) => ({
},
});
const handleCardUpdate = (card) => ({
const handleCardUpdate = (card, isFetched, cardMemberships, cardLabels, tasks, attachments) => ({
type: ActionTypes.CARD_UPDATE_HANDLE,
payload: {
card,
isFetched,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
+1 -1
View File
@@ -13,7 +13,7 @@ io.sails.environment = process.env.NODE_ENV;
const { socket } = io;
socket.path = `${Config.BASE_PATH}/socket.io`;
socket.path = `${Config.SERVER_BASE_PATH}/socket.io`;
socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
+2 -1
View File
@@ -5,6 +5,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react';
import { useClosableForm, useField } from '../../hooks';
import { focusEnd } from '../../utils/element-helpers';
import styles from './NameEdit.module.scss';
@@ -79,7 +80,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => {
if (isOpened) {
field.current.ref.current.focus();
focusEnd(field.current.ref.current);
}
}, [isOpened]);
@@ -6,6 +6,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react';
import { useForm } from '../../../hooks';
import { focusEnd } from '../../../utils/element-helpers';
import styles from './CommentEdit.module.scss';
@@ -70,7 +71,7 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
useEffect(() => {
if (isOpened) {
textField.current.ref.current.focus();
focusEnd(textField.current.ref.current);
}
}, [isOpened]);
+18 -1
View File
@@ -1,4 +1,4 @@
import React, { useCallback, useRef } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
@@ -81,6 +81,7 @@ const CardModal = React.memo(
onClose,
}) => {
const [t] = useTranslation();
const [isLinkCopied, setIsLinkCopied] = useState(false);
const isGalleryOpened = useRef(false);
@@ -146,6 +147,14 @@ const CardModal = React.memo(
onClose();
}, [onDuplicate, onClose]);
const handleCopyLinkClick = useCallback(() => {
navigator.clipboard.writeText(window.location.href);
setIsLinkCopied(true);
setTimeout(() => {
setIsLinkCopied(false);
}, 5000);
}, []);
const handleGalleryOpen = useCallback(() => {
isGalleryOpened.current = true;
}, []);
@@ -506,6 +515,14 @@ const CardModal = React.memo(
<Icon name="copy outline" className={styles.actionIcon} />
{t('action.duplicate')}
</Button>
<Button fluid className={styles.actionButton} onClick={handleCopyLinkClick}>
<Icon name={isLinkCopied ? 'linkify' : 'unlink'} className={styles.actionIcon} />
{isLinkCopied
? t('common.linkIsCopied')
: t('action.copyLink', {
context: 'title',
})}
</Button>
<DeletePopup
title="common.deleteCard"
content="common.areYouSureYouWantToDeleteThisCard"
@@ -20,7 +20,6 @@
.actionIcon {
color: #17394d;
display: inline;
margin-right: 8px;
}
@@ -5,6 +5,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react';
import { useField } from '../../../hooks';
import { focusEnd } from '../../../utils/element-helpers';
import styles from './NameEdit.module.scss';
@@ -65,7 +66,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => {
if (isOpened) {
field.current.ref.current.focus();
focusEnd(field.current.ref.current);
}
}, [isOpened]);
+2 -1
View File
@@ -4,6 +4,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { TextArea } from 'semantic-ui-react';
import { useField } from '../../hooks';
import { focusEnd } from '../../utils/element-helpers';
import styles from './NameEdit.module.scss';
@@ -71,7 +72,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => {
if (isOpened) {
field.current.ref.current.select();
focusEnd(field.current.ref.current);
}
}, [isOpened]);
-3
View File
@@ -195,9 +195,6 @@ export default {
CARD_UPDATE__SUCCESS: 'CARD_UPDATE__SUCCESS',
CARD_UPDATE__FAILURE: 'CARD_UPDATE__FAILURE',
CARD_UPDATE_HANDLE: 'CARD_UPDATE_HANDLE',
CARD_TRANSFER: 'CARD_TRANSFER',
CARD_TRANSFER__SUCCESS: 'CARD_TRANSFER__SUCCESS',
CARD_TRANSFER__FAILURE: 'CARD_TRANSFER__FAILURE',
CARD_DUPLICATE: 'CARD_DUPLICATE',
CARD_DUPLICATE__SUCCESS: 'CARD_DUPLICATE__SUCCESS',
CARD_DUPLICATE__FAILURE: 'CARD_DUPLICATE__FAILURE',
+2
View File
@@ -6,6 +6,7 @@ const BASE_PATH = BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
const SERVER_BASE_URL =
process.env.REACT_APP_SERVER_BASE_URL ||
(process.env.NODE_ENV === 'production' ? BASE_URL : 'http://localhost:1337');
const SERVER_BASE_PATH = SERVER_BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
const SERVER_HOST_NAME = SERVER_BASE_URL.replace(/^(.*\/\/[^/?#]*).*$/, '$1');
@@ -20,6 +21,7 @@ export default {
VERSION,
BASE_PATH,
SERVER_BASE_URL,
SERVER_BASE_PATH,
SERVER_HOST_NAME,
ACCESS_TOKEN_KEY,
ACCESS_TOKEN_VERSION_KEY,
+2 -1
View File
@@ -6,12 +6,13 @@ import entryActions from '../entry-actions';
import Projects from '../components/Projects';
const mapStateToProps = (state) => {
const { allowAllToCreateProjects } = selectors.selectConfig(state);
const { isAdmin } = selectors.selectCurrentUser(state);
const projects = selectors.selectProjectsForCurrentUser(state);
return {
items: projects,
canAdd: isAdmin,
canAdd: allowAllToCreateProjects || isAdmin,
};
};
+4
View File
@@ -104,6 +104,7 @@ export default {
language: 'Sprache',
leaveBoard_title: 'Board verlassen',
leaveProject_title: 'Projekt verlassen',
linkIsCopied: 'Link kopiert',
list: 'Listen',
listActions_title: 'Aufgaben auflisten',
managers: 'Manager',
@@ -169,6 +170,7 @@ export default {
addTask: 'Aufgabe hinzufügen',
addToCard: 'Zu Karte hinzufügen',
addUser: 'Benutzer hinzufügen',
copyLink_title: 'Link kopieren',
createBoard: 'Board erstellen',
createFile: 'Datei erstellen',
createLabel: 'Label erstellen',
@@ -190,6 +192,8 @@ export default {
deleteTask: 'Aufgabe löschen',
deleteTask_title: 'Aufgabe löschen',
deleteUser: 'Benutzer löschen',
duplicate: 'Kopieren',
duplicateCard_title: 'Karte kopieren',
edit: 'Bearbeiten',
editDueDate_title: 'Fälligkeitsdatum bearbeiten',
editDescription_title: 'Beschreibung ändern',
+2
View File
@@ -106,6 +106,7 @@ export default {
language: 'Language',
leaveBoard_title: 'Leave Board',
leaveProject_title: 'Leave Project',
linkIsCopied: 'Link is copied',
list: 'List',
listActions_title: 'List Actions',
managers: 'Managers',
@@ -183,6 +184,7 @@ export default {
addTask: 'Add task',
addToCard: 'Add to card',
addUser: 'Add user',
copyLink_title: 'Copy Link',
createBoard: 'Create board',
createFile: 'Create file',
createLabel: 'Create label',
+27 -4
View File
@@ -15,6 +15,16 @@ export default {
translation: {
common: {
aboutPlanka: 'Plankáról',
account: 'Fiók',
actions: 'Műveletek',
addAttachment_title: 'Melléklet hozzáadása',
addComment: 'Megjegyzés hozzáadása',
addManager_title: 'Menedzser hozzáadása',
addMember_title: 'Tag létrehozása',
addUser_title: 'Felhasználó hozzáadása',
administrator: 'Rendszergazda',
all: 'Összes',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'Az összes változás automatikusan mentésre kerül a kapcsolat helyreállása után.',
areYouSureYouWantToDeleteThisAttachment: 'Biztosan törölni szeretné ezt a mellékletet?',
@@ -29,7 +39,7 @@ export default {
areYouSureYouWantToLeaveBoard: 'Biztosan el akarja hagyni a táblát?',
areYouSureYouWantToLeaveProject: 'Biztosan el akarja hagyni a projektet?',
areYouSureYouWantToRemoveThisManagerFromProject:
'Biztosan eltávolítja ezt a vezetőt a projektből?',
'Biztosan eltávolítja ezt a menedzsert a projektből?',
areYouSureYouWantToRemoveThisMemberFromBoard: 'Biztosan eltávolítja ezt a tagot a tábláról?',
attachment: 'Melléklet',
attachments: 'Mellékletek',
@@ -44,6 +54,7 @@ export default {
cardNotFound_title: 'Kártya nem található',
cardOrActionAreDeleted: 'A kártya vagy művelet törölve lett.',
color: 'Szín',
copy_inline: 'másolás',
createBoard_title: 'Tábla létrehozása',
createLabel_title: 'Címke létrehozása',
createNewOneOrSelectExistingOne: 'Hozzon létre egy újat, vagy válasszon ki egy meglévőt.',
@@ -52,6 +63,7 @@ export default {
currentPassword: 'Jelenlegi jelszó',
dangerZone_title: 'Veszélyzóna',
date: 'Dátum',
dueDate: 'Határidő',
dueDate_title: 'Esedékesség dátuma',
deleteAttachment_title: 'Melléklet törlése',
deleteBoard_title: 'Tábla törlése',
@@ -97,13 +109,17 @@ export default {
language: 'Nyelv',
leaveBoard_title: 'Tábla elhagyása',
leaveProject_title: 'Projekt elhagyása',
linkIsCopied: 'Link másolva',
list: 'Lista',
listActions_title: 'Lista műveletek',
managers: 'Vezetők',
managers: 'Menedzserek',
managerActions_title: 'Menedzseri műveletek',
members: 'Tagok',
memberActions_title: 'Tag műveletek',
minutes: 'Percek',
moveCard_title: 'Kártya áthelyezése',
name: 'Név',
newestFirst: 'Újabbak előre',
newEmail: 'Új e-mail',
newPassword: 'Új jelszó',
newUsername: 'Új felhasználónév',
@@ -113,6 +129,7 @@ export default {
noProjects: 'Nincsenek projektek',
notifications: 'Értesítések',
noUnreadNotifications: 'Nincsenek olvasatlan értesítések.',
oldestFirst: 'Régebbi előre',
openBoard_title: 'Tábla megnyitása',
optional_inline: 'opcionális',
organization: 'Szervezet',
@@ -122,17 +139,19 @@ export default {
'Tipp: nyomja meg a Ctrl-V (Cmd-V a Mac-en) billentyűkombinációt a vágólapról történő melléklet hozzáadásához.',
project: 'Projekt',
projectNotFound_title: 'Projekt nem található',
removeManager_title: 'Vezető eltávolítása',
removeManager_title: 'Menedzser eltávolítása',
removeMember_title: 'Tag eltávolítása',
searchLabels: 'Címkék keresése...',
searchMembers: 'Tagok keresése...',
searchUsers: 'Felhasználók keresése...',
searchCards: 'Kártyák keresése..',
seconds: 'Másodpercek',
selectBoard: 'Válassza ki a táblát',
selectList: 'Válassza ki a listát',
selectPermissions_title: 'Jogosultságok kiválasztása',
selectProject: 'Válassza ki a projektet',
settings: 'Beállítások',
sortList_title: 'Rendezés listában',
stopwatch: 'Stopper',
subscribeToMyOwnCardsByDefault: 'Alapértelmezés szerint feliratkozás a saját kártyáimra',
taskActions_title: 'Feladat műveletek',
@@ -170,6 +189,7 @@ export default {
addTask: 'Feladat hozzáadása',
addToCard: 'Kártyához hozzáadása',
addUser: 'Felhasználó hozzáadása',
copyLink_title: 'Link másolása',
createBoard: 'Tábla létrehozása',
createFile: 'Fájl létrehozása',
createLabel: 'Címke létrehozása',
@@ -191,6 +211,8 @@ export default {
deleteTask: 'Feladat törlése',
deleteTask_title: 'Feladat törlése',
deleteUser: 'Felhasználó törlése',
duplicate: 'Másolás',
duplicateCard_title: 'Kártya másolása',
edit: 'Szerkesztés',
editDueDate_title: 'Esedékesség dátumának szerkesztése',
editDescription_title: 'Leírás szerkesztése',
@@ -214,12 +236,13 @@ export default {
removeCover_title: 'Borító eltávolítása',
removeFromBoard: 'Eltávolítás a tábláról',
removeFromProject: 'Eltávolítás a projektről',
removeManager: 'Vezető eltávolítása',
removeManager: 'Menedzser eltávolítása',
removeMember: 'Tag eltávolítása',
save: 'Mentés',
showAllAttachments: 'Összes melléklet megjelenítése ({{hidden}} rejtve)',
showDetails: 'Részletek megjelenítése',
showFewerAttachments: 'Kevesebb melléklet megjelenítése',
sortList_title: 'Rendezés listában',
start: 'Indítás',
stop: 'Megállítás',
subscribe: 'Feliratkozás',
+2
View File
@@ -142,6 +142,7 @@ export default {
searchLabels: 'Поиск меток...',
searchMembers: 'Поиск участников...',
searchUsers: 'Поиск пользователей...',
searchCards: 'Поиск карточек...',
seconds: 'Секунды',
selectBoard: 'Выберите доску',
selectList: 'Выберите список',
@@ -232,6 +233,7 @@ export default {
showAllAttachments: 'Показать все вложения ({{hidden}} скрыто)',
showDetails: 'Показать подробности',
showFewerAttachments: 'Показать меньше вложений',
sortList_title: 'Сортировать список',
start: 'Начать',
stop: 'Остановить',
subscribe: 'Подписаться',
+1
View File
@@ -28,6 +28,7 @@ export default class extends BaseModel {
case ActionTypes.CORE_INITIALIZE:
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
case ActionTypes.CARD_UPDATE_HANDLE:
if (payload.attachments) {
payload.attachments.forEach((attachment) => {
Attachment.upsert(attachment);
+7 -4
View File
@@ -1,3 +1,4 @@
import orderBy from 'lodash/orderBy';
import { attr, fk, many } from 'redux-orm';
import BaseModel from './BaseModel';
@@ -218,10 +219,6 @@ export default class extends BaseModel {
}
}
getOrderedMembershipsQuerySet() {
return this.memberships.orderBy('createdAt');
}
getOrderedLabelsQuerySet() {
return this.labels.orderBy('position');
}
@@ -230,6 +227,12 @@ export default class extends BaseModel {
return this.lists.orderBy('position');
}
getOrderedMembershipsModelArray() {
return orderBy(this.memberships.toModelArray(), (boardMembershipModel) =>
boardMembershipModel.user.name.toLocaleLowerCase(),
);
}
getMembershipModelForUser(userId) {
return this.memberships
.filter({
+34 -3
View File
@@ -180,7 +180,6 @@ export default class extends BaseModel {
break;
case ActionTypes.CARD_CREATE:
case ActionTypes.CARD_UPDATE__SUCCESS:
case ActionTypes.CARD_UPDATE_HANDLE:
Card.upsert(payload.card);
break;
@@ -202,8 +201,40 @@ export default class extends BaseModel {
break;
}
case ActionTypes.CARD_UPDATE:
Card.withId(payload.id).update(payload.data);
case ActionTypes.CARD_UPDATE: {
const cardModel = Card.withId(payload.id);
// TODO: introduce separate action?
if (payload.data.boardId && payload.data.boardId !== cardModel.boardId) {
cardModel.deleteWithRelated();
} else {
cardModel.update(payload.data);
}
break;
}
case ActionTypes.CARD_UPDATE_HANDLE:
if (payload.isFetched) {
const cardModel = Card.withId(payload.card.id);
if (cardModel) {
cardModel.deleteWithRelated();
}
}
Card.upsert(payload.card);
if (payload.cardMemberships) {
payload.cardMemberships.forEach(({ cardId, userId }) => {
Card.withId(cardId).users.add(userId);
});
}
if (payload.cardLabels) {
payload.cardLabels.forEach(({ cardId, labelId }) => {
Card.withId(cardId).labels.add(labelId);
});
}
break;
case ActionTypes.CARD_DUPLICATE: {
+1
View File
@@ -27,6 +27,7 @@ export default class extends BaseModel {
case ActionTypes.CORE_INITIALIZE:
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
case ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE:
case ActionTypes.CARD_UPDATE_HANDLE:
if (payload.tasks) {
payload.tasks.forEach((task) => {
Task.upsert(task);
+1 -1
View File
@@ -298,7 +298,7 @@ export default class extends BaseModel {
static getOrderedUndeletedQuerySet() {
return this.filter({
deletedAt: null,
}).orderBy('createdAt');
}).orderBy((user) => user.name.toLocaleLowerCase());
}
getOrderedProjectManagersQuerySet() {
+23 -2
View File
@@ -81,9 +81,30 @@ export function* updateCurrentCard(data) {
yield call(updateCard, cardId, data);
}
// TODO: handle card transfer
export function* handleCardUpdate(card) {
yield put(actions.handleCardUpdate(card));
let fetch = false;
if (card.boardId) {
const prevCard = yield select(selectors.selectCardById, card.id);
fetch = !prevCard || prevCard.boardId !== card.boardId;
}
let cardMemberships;
let cardLabels;
let tasks;
let attachments;
if (fetch) {
try {
({
item: card, // eslint-disable-line no-param-reassign
included: { cardMemberships, cardLabels, tasks, attachments },
} = yield call(request, api.getCard, card.id));
} catch (error) {
fetch = false;
}
}
yield put(actions.handleCardUpdate(card, fetch, cardMemberships, cardLabels, tasks, attachments));
}
export function* moveCard(id, listId, index = 0) {
+1 -4
View File
@@ -55,10 +55,7 @@ export const selectMembershipsForCurrentBoard = createSelector(
return boardModel;
}
return boardModel
.getOrderedMembershipsQuerySet()
.toModelArray()
.map((boardMembershipModel) => ({
return boardModel.getOrderedMembershipsModelArray().map((boardMembershipModel) => ({
...boardMembershipModel.ref,
isPersisted: !isLocalId(boardMembershipModel.id),
user: {
+5
View File
@@ -0,0 +1,5 @@
// eslint-disable-next-line import/prefer-default-export
export const focusEnd = (element) => {
element.focus();
element.setSelectionRange(element.value.length + 1, element.value.length + 1);
};
+15 -4
View File
@@ -1,7 +1,6 @@
version: '3.8'
services:
server:
build:
context: ./server
@@ -13,6 +12,7 @@ services:
- NODE_ENV=development
- DATABASE_URL=postgresql://user:password@postgres:5432/planka_db
- SECRET_KEY=notsecretkey
# - TRUST_PROXY=0
# - TOKEN_EXPIRES_IN=365 # In days
@@ -23,6 +23,9 @@ services:
# Configure knex to accept SSL certificates
# - KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE=false
# - ALLOW_ALL_TO_CREATE_PROJECTS=true
# - OIDC_ISSUER=
# - OIDC_CLIENT_ID=
# - OIDC_CLIENT_SECRET=
@@ -39,14 +42,23 @@ services:
# Email Notifications (https://nodemailer.com/smtp/)
# - SMTP_HOST=
# - SMTP_PORT=587
# - SMTP_NAME=
# - SMTP_SECURE=true
# - SMTP_USER=
# - SMTP_PASSWORD=
# - SMTP_FROM="Demo Demo" <demo@demo.demo>
# Optional fields: accessToken, events, excludedEvents
# - |
# WEBHOOKS=[{
# "url": "http://localhost:3001",
# "accessToken": "notaccesstoken",
# "events": ["cardCreate", "cardUpdate", "cardDelete"],
# "excludedEvents": ["notificationCreate", "notificationUpdate"]
# }]
# - SLACK_BOT_TOKEN=
# - SLACK_CHANNEL_ID=
working_dir: /app
command: ["sh", "-c", "npm run start"]
depends_on:
@@ -75,11 +87,11 @@ services:
dockerfile: ../config/development/Dockerfile.server
environment:
- DATABASE_URL=postgresql://user:password@postgres:5432/planka_db
# - DEFAULT_ADMIN_EMAIL=demo@demo.demo # Do not remove if you want to prevent this user from being edited/deleted
# - DEFAULT_ADMIN_PASSWORD=demo
# - DEFAULT_ADMIN_NAME=Demo Demo
# - DEFAULT_ADMIN_USERNAME=demo
working_dir: /app
command: ["sh", "-c", "npm run db:init"]
volumes:
@@ -116,6 +128,5 @@ services:
- server
- client
volumes:
db-data:
+12
View File
@@ -31,6 +31,8 @@ services:
# - DEFAULT_ADMIN_NAME=Demo Demo
# - DEFAULT_ADMIN_USERNAME=demo
# - ALLOW_ALL_TO_CREATE_PROJECTS=true
# - OIDC_ISSUER=
# - OIDC_CLIENT_ID=
# - OIDC_CLIENT_SECRET=
@@ -47,11 +49,21 @@ services:
# Email Notifications (https://nodemailer.com/smtp/)
# - SMTP_HOST=
# - SMTP_PORT=587
# - SMTP_NAME=
# - SMTP_SECURE=true
# - SMTP_USER=
# - SMTP_PASSWORD=
# - SMTP_FROM="Demo Demo" <demo@demo.demo>
# Optional fields: accessToken, events, excludedEvents
# - |
# WEBHOOKS=[{
# "url": "http://localhost:3001",
# "accessToken": "notaccesstoken",
# "events": ["cardCreate", "cardUpdate", "cardDelete"],
# "excludedEvents": ["notificationCreate", "notificationUpdate"]
# }]
# - SLACK_BOT_TOKEN=
# - SLACK_CHANNEL_ID=
depends_on:
+80 -71
View File
@@ -1,39 +1,30 @@
{
"name": "planka",
"version": "1.17.3",
"version": "1.19.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "planka",
"version": "1.17.3",
"version": "1.19.2",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
"concurrently": "^8.2.2",
"husky": "^8.0.3",
"lint-staged": "^15.2.2"
"husky": "^9.0.1",
"lint-staged": "^15.2.5"
},
"devDependencies": {
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"prettier": "^3.2.5"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
"integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
"prettier": "^3.3.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
"integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz",
"integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -276,11 +267,11 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -427,11 +418,11 @@
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="
},
"node_modules/commander": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"engines": {
"node": ">=16"
"node": ">=18"
}
},
"node_modules/concat-map": {
@@ -495,9 +486,9 @@
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"dependencies": {
"ms": "2.1.2"
},
@@ -810,9 +801,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -896,6 +887,7 @@
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
@@ -962,14 +954,14 @@
}
},
"node_modules/husky": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz",
"integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==",
"version": "9.0.11",
"resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz",
"integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==",
"bin": {
"husky": "lib/bin.js"
"husky": "bin.mjs"
},
"engines": {
"node": ">=14"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
@@ -1013,6 +1005,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true,
"dependencies": {
"once": "^1.3.0",
@@ -1143,28 +1136,31 @@
}
},
"node_modules/lilconfig": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz",
"integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
"integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antonk52"
}
},
"node_modules/lint-staged": {
"version": "15.2.2",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz",
"integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==",
"version": "15.2.5",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.5.tgz",
"integrity": "sha512-j+DfX7W9YUvdzEZl3Rk47FhDF6xwDBV5wwsCPw6BwWZVPYJemusQmvb9bRsW23Sqsaa+vRloAWogbK4BUuU2zA==",
"dependencies": {
"chalk": "5.3.0",
"commander": "11.1.0",
"debug": "4.3.4",
"execa": "8.0.1",
"lilconfig": "3.0.0",
"listr2": "8.0.1",
"micromatch": "4.0.5",
"pidtree": "0.6.0",
"string-argv": "0.3.2",
"yaml": "2.3.4"
"chalk": "~5.3.0",
"commander": "~12.1.0",
"debug": "~4.3.4",
"execa": "~8.0.1",
"lilconfig": "~3.1.1",
"listr2": "~8.2.1",
"micromatch": "~4.0.7",
"pidtree": "~0.6.0",
"string-argv": "~0.3.2",
"yaml": "~2.4.2"
},
"bin": {
"lint-staged": "bin/lint-staged.js"
@@ -1188,15 +1184,15 @@
}
},
"node_modules/listr2": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz",
"integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==",
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz",
"integrity": "sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g==",
"dependencies": {
"cli-truncate": "^4.0.0",
"colorette": "^2.0.20",
"eventemitter3": "^5.0.1",
"log-update": "^6.0.0",
"rfdc": "^1.3.0",
"rfdc": "^1.3.1",
"wrap-ansi": "^9.0.0"
},
"engines": {
@@ -1318,11 +1314,11 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"dependencies": {
"braces": "^3.0.2",
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
@@ -1412,17 +1408,17 @@
}
},
"node_modules/optionator": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
"integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"dev": true,
"dependencies": {
"@aashutoshrathi/word-wrap": "^1.2.3",
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
"type-check": "^0.4.0"
"type-check": "^0.4.0",
"word-wrap": "^1.2.5"
},
"engines": {
"node": ">= 0.8.0"
@@ -1528,9 +1524,9 @@
}
},
"node_modules/prettier": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.0.tgz",
"integrity": "sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
@@ -1666,6 +1662,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
@@ -1967,6 +1964,15 @@
"node": ">= 8"
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/wrap-ansi": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
@@ -2034,9 +2040,12 @@
}
},
"node_modules/yaml": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
"integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14"
}
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "planka",
"version": "1.17.3",
"version": "1.19.2",
"private": true,
"homepage": "https://plankanban.github.io/planka",
"repository": {
@@ -18,7 +18,7 @@
"docker:build:base": "docker build -t ghcr.io/plankanban/planka:base-local -f Dockerfile.base .",
"postinstall": "(cd server && npm i && cd ../client && npm i)",
"lint": "npm run server:lint && npm run client:lint",
"prepare": "husky install",
"prepare": "husky",
"server:console": "npm run console --prefix server",
"server:db:init": "npm run db:init --prefix server",
"server:db:migrate": "npm run db:migrate --prefix server",
@@ -57,13 +57,13 @@
},
"dependencies": {
"concurrently": "^8.2.2",
"husky": "^8.0.3",
"lint-staged": "^15.2.2"
"husky": "^9.0.1",
"lint-staged": "^15.2.5"
},
"devDependencies": {
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"prettier": "^3.2.5"
"prettier": "^3.3.0"
}
}
+10
View File
@@ -22,6 +22,8 @@ SECRET_KEY=notsecretkey
# DEFAULT_ADMIN_NAME=Demo Demo
# DEFAULT_ADMIN_USERNAME=demo
# ALLOW_ALL_TO_CREATE_PROJECTS=true
# OIDC_ISSUER=
# OIDC_CLIENT_ID=
# OIDC_CLIENT_SECRET=
@@ -43,6 +45,14 @@ SECRET_KEY=notsecretkey
# SMTP_PASSWORD=
# SMTP_FROM="Demo Demo" <demo@demo.demo>
# Optional fields: accessToken, events, excludedEvents
# WEBHOOKS='[{
# "url": "http://localhost:3001",
# "accessToken": "notaccesstoken",
# "events": ["cardCreate", "cardUpdate", "cardDelete"],
# "excludedEvents": ["notificationCreate", "notificationUpdate"]
# }]'
# SLACK_BOT_TOKEN=
# SLACK_CHANNEL_ID=
+5 -2
View File
@@ -44,12 +44,12 @@ module.exports = {
async fn(inputs, exits) {
const { currentUser } = this.req;
const { card } = await sails.helpers.cards
const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
const boardMembership = await BoardMembership.findOne({
boardId: card.boardId,
boardId: board.id,
userId: currentUser.id,
});
@@ -83,6 +83,9 @@ module.exports = {
const fileData = await sails.helpers.attachments.processUploadedFile(file);
const attachment = await sails.helpers.attachments.createOne.with({
project,
board,
list,
values: {
...fileData,
card,
+4 -1
View File
@@ -33,7 +33,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.ATTACHMENT_NOT_FOUND);
let { attachment } = path;
const { card, board } = path;
const { card, list, board, project } = path;
const boardMembership = await BoardMembership.findOne({
boardId: board.id,
@@ -49,9 +49,12 @@ module.exports = {
}
attachment = await sails.helpers.attachments.deleteOne.with({
project,
board,
list,
card,
record: attachment,
actorUser: currentUser,
request: this.req,
});
+5 -1
View File
@@ -37,7 +37,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.ATTACHMENT_NOT_FOUND);
let { attachment } = path;
const { board } = path;
const { card, list, board, project } = path;
const boardMembership = await BoardMembership.findOne({
boardId: board.id,
@@ -56,8 +56,12 @@ module.exports = {
attachment = await sails.helpers.attachments.updateOne.with({
values,
project,
board,
list,
card,
record: attachment,
actorUser: currentUser,
request: this.req,
});
@@ -48,14 +48,11 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { board } = await sails.helpers.boards
const { board, project } = await sails.helpers.boards
.getProjectPath(inputs.boardId)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
const isProjectManager = await sails.helpers.users.isProjectManager(
currentUser.id,
board.projectId,
);
const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
if (!isProjectManager) {
throw Errors.BOARD_NOT_FOUND; // Forbidden
@@ -71,11 +68,13 @@ module.exports = {
const boardMembership = await sails.helpers.boardMemberships.createOne
.with({
project,
values: {
...values,
board,
user,
},
actorUser: currentUser,
request: this.req,
})
.intercept('userAlreadyBoardMember', () => Errors.USER_ALREADY_BOARD_MEMBER);
@@ -27,7 +27,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.BOARD_MEMBERSHIP_NOT_FOUND);
let { boardMembership } = path;
const { project } = path;
const { board, project } = path;
if (boardMembership.userId !== currentUser.id) {
const isProjectManager = await sails.helpers.users.isProjectManager(
@@ -42,7 +42,9 @@ module.exports = {
boardMembership = await sails.helpers.boardMemberships.deleteOne.with({
project,
board,
record: boardMembership,
actorUser: currentUser,
request: this.req,
});
@@ -35,7 +35,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.BOARD_MEMBERSHIP_NOT_FOUND);
let { boardMembership } = path;
const { project } = path;
const { board, project } = path;
const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
@@ -47,7 +47,10 @@ module.exports = {
boardMembership = await sails.helpers.boardMemberships.updateOne.with({
values,
project,
board,
record: boardMembership,
actorUser: currentUser,
request: this.req,
});
+1 -1
View File
@@ -103,7 +103,7 @@ module.exports = {
project,
},
import: boardImport,
user: currentUser,
actorUser: currentUser,
requestId: inputs.requestId,
request: this.req,
});
+7 -5
View File
@@ -22,25 +22,27 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
let { board } = await sails.helpers.boards
const path = await sails.helpers.boards
.getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
let { board } = path;
const { project } = path;
if (!board) {
throw Errors.BOARD_NOT_FOUND;
}
const isProjectManager = await sails.helpers.users.isProjectManager(
currentUser.id,
board.projectId,
);
const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
if (!isProjectManager) {
throw Errors.BOARD_NOT_FOUND; // Forbidden
}
board = await sails.helpers.boards.deleteOne.with({
project,
record: board,
actorUser: currentUser,
request: this.req,
});
+7 -5
View File
@@ -29,18 +29,18 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
let { board } = await sails.helpers.boards
const path = await sails.helpers.boards
.getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
let { board } = path;
const { project } = path;
if (!board) {
throw Errors.BOARD_NOT_FOUND;
}
const isProjectManager = await sails.helpers.users.isProjectManager(
currentUser.id,
board.projectId,
);
const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
if (!isProjectManager) {
throw Errors.BOARD_NOT_FOUND; // Forbidden
@@ -50,7 +50,9 @@ module.exports = {
board = await sails.helpers.boards.updateOne.with({
values,
project,
record: board,
actorUser: currentUser,
request: this.req,
});
+7 -3
View File
@@ -45,12 +45,12 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { card } = await sails.helpers.cards
const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
const boardMembership = await BoardMembership.findOne({
boardId: card.boardId,
boardId: board.id,
userId: currentUser.id,
});
@@ -64,7 +64,7 @@ module.exports = {
const label = await Label.findOne({
id: inputs.labelId,
boardId: card.boardId,
boardId: board.id,
});
if (!label) {
@@ -73,10 +73,14 @@ module.exports = {
const cardLabel = await sails.helpers.cardLabels.createOne
.with({
project,
board,
list,
values: {
card,
label,
},
actorUser: currentUser,
request: this.req,
})
.intercept('labelAlreadyInCard', () => Errors.LABEL_ALREADY_IN_CARD);
+5 -1
View File
@@ -39,7 +39,7 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { board } = await sails.helpers.cards
const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
@@ -66,8 +66,12 @@ module.exports = {
}
cardLabel = await sails.helpers.cardLabels.deleteOne.with({
project,
board,
list,
card,
record: cardLabel,
actorUser: currentUser,
request: this.req,
});
@@ -45,12 +45,12 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { card } = await sails.helpers.cards
const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
const boardMembership = await BoardMembership.findOne({
boardId: card.boardId,
boardId: board.id,
userId: currentUser.id,
});
@@ -62,7 +62,7 @@ module.exports = {
throw Errors.NOT_ENOUGH_RIGHTS;
}
const isBoardMember = await sails.helpers.users.isBoardMember(inputs.userId, card.boardId);
const isBoardMember = await sails.helpers.users.isBoardMember(inputs.userId, board.id);
if (!isBoardMember) {
throw Errors.USER_NOT_FOUND;
@@ -70,10 +70,14 @@ module.exports = {
const cardMembership = await sails.helpers.cardMemberships.createOne
.with({
project,
board,
list,
values: {
card,
userId: inputs.userId,
},
actorUser: currentUser,
request: this.req,
})
.intercept('userAlreadyCardMember', () => Errors.USER_ALREADY_CARD_MEMBER);
@@ -39,7 +39,7 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { board } = await sails.helpers.cards
const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
@@ -66,8 +66,12 @@ module.exports = {
}
cardMembership = await sails.helpers.cardMemberships.deleteOne.with({
project,
board,
list,
card,
record: cardMembership,
actorUser: currentUser,
request: this.req,
});
+2 -1
View File
@@ -78,7 +78,7 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { board, list } = await sails.helpers.lists
const { list, board, project } = await sails.helpers.lists
.getProjectPath(inputs.listId)
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
@@ -99,6 +99,7 @@ module.exports = {
const card = await sails.helpers.cards.createOne
.with({
project,
board,
values: {
...values,
+9 -3
View File
@@ -28,12 +28,15 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
let { card } = await sails.helpers.cards
const path = await sails.helpers.cards
.getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
let { card } = path;
const { list, board, project } = path;
const boardMembership = await BoardMembership.findOne({
boardId: card.boardId,
boardId: board.id,
userId: currentUser.id,
});
@@ -46,8 +49,11 @@ module.exports = {
}
card = await sails.helpers.cards.deleteOne.with({
project,
board,
list,
record: card,
user: currentUser,
actorUser: currentUser,
request: this.req,
});
+3 -2
View File
@@ -35,12 +35,12 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { card, list, board } = await sails.helpers.cards
const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
const boardMembership = await BoardMembership.findOne({
boardId: card.boardId,
boardId: board.id,
userId: currentUser.id,
});
@@ -60,6 +60,7 @@ module.exports = {
cardLabels,
tasks,
} = await sails.helpers.cards.duplicateOne.with({
project,
board,
list,
record: card,
+7 -3
View File
@@ -118,7 +118,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
let { card } = path;
const { list, board } = path;
const { list, board, project } = path;
let boardMembership = await BoardMembership.findOne({
boardId: board.id,
@@ -133,9 +133,11 @@ module.exports = {
throw Errors.NOT_ENOUGH_RIGHTS;
}
let nextProject;
let nextBoard;
if (!_.isUndefined(inputs.boardId)) {
({ board: nextBoard } = await sails.helpers.boards
({ board: nextBoard, project: nextProject } = await sails.helpers.boards
.getProjectPath(inputs.boardId)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND));
@@ -177,15 +179,17 @@ module.exports = {
card = await sails.helpers.cards.updateOne
.with({
project,
board,
list,
record: card,
values: {
...values,
project: nextProject,
board: nextBoard,
list: nextList,
},
user: currentUser,
actorUser: currentUser,
request: this.req,
})
.intercept('positionMustBeInValues', () => Errors.POSITION_MUST_BE_PRESENT)
@@ -32,7 +32,7 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { board, card } = await sails.helpers.cards
const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
@@ -55,7 +55,9 @@ module.exports = {
};
const action = await sails.helpers.actions.createOne.with({
project,
board,
list,
values: {
...values,
card,
@@ -36,7 +36,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.COMMENT_ACTION_NOT_FOUND);
let { action } = path;
const { board, project } = path;
const { card, list, board, project } = path;
const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
@@ -60,8 +60,12 @@ module.exports = {
}
action = await sails.helpers.actions.deleteOne.with({
project,
board,
list,
card,
record: action,
actorUser: currentUser,
request: this.req,
});
@@ -40,7 +40,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.COMMENT_ACTION_NOT_FOUND);
let { action } = path;
const { board, project } = path;
const { card, list, board, project } = path;
const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
@@ -69,8 +69,12 @@ module.exports = {
action = await sails.helpers.actions.updateOne.with({
values,
project,
board,
list,
card,
record: action,
actorUser: currentUser,
request: this.req,
});
+3 -1
View File
@@ -42,7 +42,7 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { board } = await sails.helpers.boards
const { board, project } = await sails.helpers.boards
.getProjectPath(inputs.boardId)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
@@ -62,10 +62,12 @@ module.exports = {
const values = _.pick(inputs, ['position', 'name', 'color']);
const label = await sails.helpers.labels.createOne.with({
project,
values: {
...values,
board,
},
actorUser: currentUser,
request: this.req,
});
+8 -2
View File
@@ -28,12 +28,15 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
let { label } = await sails.helpers.labels
const path = await sails.helpers.labels
.getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.LABEL_NOT_FOUND);
let { label } = path;
const { board, project } = path;
const boardMembership = await BoardMembership.findOne({
boardId: label.boardId,
boardId: board.id,
userId: currentUser.id,
});
@@ -46,7 +49,10 @@ module.exports = {
}
label = await sails.helpers.labels.deleteOne.with({
project,
board,
record: label,
actorUser: currentUser,
request: this.req,
});
+8 -2
View File
@@ -40,12 +40,15 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
let { label } = await sails.helpers.labels
const path = await sails.helpers.labels
.getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.LABEL_NOT_FOUND);
let { label } = path;
const { board, project } = path;
const boardMembership = await BoardMembership.findOne({
boardId: label.boardId,
boardId: board.id,
userId: currentUser.id,
});
@@ -61,7 +64,10 @@ module.exports = {
label = await sails.helpers.labels.updateOne.with({
values,
project,
board,
record: label,
actorUser: currentUser,
request: this.req,
});
+3 -1
View File
@@ -36,7 +36,7 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { board } = await sails.helpers.boards
const { board, project } = await sails.helpers.boards
.getProjectPath(inputs.boardId)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
@@ -56,10 +56,12 @@ module.exports = {
const values = _.pick(inputs, ['position', 'name']);
const list = await sails.helpers.lists.createOne.with({
project,
values: {
...values,
board,
},
actorUser: currentUser,
request: this.req,
});
+8 -2
View File
@@ -28,12 +28,15 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
let { list } = await sails.helpers.lists
const path = await sails.helpers.lists
.getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
let { list } = path;
const { board, project } = path;
const boardMembership = await BoardMembership.findOne({
boardId: list.boardId,
boardId: board.id,
userId: currentUser.id,
});
@@ -46,7 +49,10 @@ module.exports = {
}
list = await sails.helpers.lists.deleteOne.with({
project,
board,
record: list,
actorUser: currentUser,
request: this.req,
});
+5 -2
View File
@@ -32,12 +32,12 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { list } = await sails.helpers.lists
const { list, board, project } = await sails.helpers.lists
.getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
const boardMembership = await BoardMembership.findOne({
boardId: list.boardId,
boardId: board.id,
userId: currentUser.id,
});
@@ -50,8 +50,11 @@ module.exports = {
}
const cards = await sails.helpers.lists.sortOne.with({
project,
board,
record: list,
type: inputs.type,
actorUser: currentUser,
request: this.req,
});
+8 -2
View File
@@ -35,12 +35,15 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
let { list } = await sails.helpers.lists
const path = await sails.helpers.lists
.getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
let { list } = path;
const { board, project } = path;
const boardMembership = await BoardMembership.findOne({
boardId: list.boardId,
boardId: board.id,
userId: currentUser.id,
});
@@ -56,7 +59,10 @@ module.exports = {
list = await sails.helpers.lists.updateOne.with({
values,
project,
board,
record: list,
actorUser: currentUser,
request: this.req,
});
@@ -18,7 +18,7 @@ module.exports = {
const notifications = await sails.helpers.notifications.updateMany.with({
values,
recordsOrIds: inputs.ids.split(','),
user: currentUser,
actorUser: currentUser,
request: this.req,
});
@@ -63,6 +63,7 @@ module.exports = {
project,
user,
},
actorUser: currentUser,
request: this.req,
})
.intercept('userAlreadyProjectManager', () => Errors.USER_ALREADY_PROJECT_MANAGER);
@@ -40,6 +40,7 @@ module.exports = {
// TODO: check if the last one
projectManager = await sails.helpers.projectManagers.deleteOne.with({
record: projectManager,
actorUser: currentUser,
request: this.req,
});
+17 -1
View File
@@ -1,3 +1,9 @@
const Errors = {
NOT_ENOUGH_RIGHTS: {
notEnoughRights: 'Not enough rights',
},
};
module.exports = {
inputs: {
name: {
@@ -6,14 +12,24 @@ module.exports = {
},
},
exits: {
notEnoughRights: {
responseType: 'forbidden',
},
},
async fn(inputs) {
const { currentUser } = this.req;
if (!currentUser.isAdmin && !sails.config.custom.allowAllToCreateProjects) {
throw Errors.NOT_ENOUGH_RIGHTS;
}
const values = _.pick(inputs, ['name']);
const { project, projectManager } = await sails.helpers.projects.createOne.with({
values,
user: currentUser,
actorUser: currentUser,
request: this.req,
});
@@ -36,6 +36,7 @@ module.exports = {
project = await sails.helpers.projects.deleteOne.with({
record: project,
actorUser: currentUser,
request: this.req,
});
@@ -90,6 +90,7 @@ module.exports = {
values: {
backgroundImage: fileData,
},
actorUser: currentUser,
request: this.req,
});
@@ -81,6 +81,7 @@ module.exports = {
project = await sails.helpers.projects.updateOne.with({
values,
record: project,
actorUser: currentUser,
request: this.req,
});
+1
View File
@@ -17,6 +17,7 @@ module.exports = {
return {
item: {
oidc,
allowAllToCreateProjects: sails.config.custom.allowAllToCreateProjects,
},
};
},
+6 -2
View File
@@ -39,12 +39,12 @@ module.exports = {
async fn(inputs) {
const { currentUser } = this.req;
const { card } = await sails.helpers.cards
const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
const boardMembership = await BoardMembership.findOne({
boardId: card.boardId,
boardId: board.id,
userId: currentUser.id,
});
@@ -59,10 +59,14 @@ module.exports = {
const values = _.pick(inputs, ['position', 'name', 'isCompleted']);
const task = await sails.helpers.tasks.createOne.with({
project,
board,
list,
values: {
...values,
card,
},
actorUser: currentUser,
request: this.req,
});
+5 -1
View File
@@ -33,7 +33,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.TASK_NOT_FOUND);
let { task } = path;
const { board } = path;
const { card, list, board, project } = path;
const boardMembership = await BoardMembership.findOne({
boardId: board.id,
@@ -49,8 +49,12 @@ module.exports = {
}
task = await sails.helpers.tasks.deleteOne.with({
project,
board,
list,
card,
record: task,
actorUser: currentUser,
request: this.req,
});
+5 -1
View File
@@ -43,7 +43,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.TASK_NOT_FOUND);
let { task } = path;
const { board } = path;
const { card, list, board, project } = path;
const boardMembership = await BoardMembership.findOne({
boardId: board.id,
@@ -62,8 +62,12 @@ module.exports = {
task = await sails.helpers.tasks.updateOne.with({
values,
project,
board,
list,
card,
record: task,
actorUser: currentUser,
request: this.req,
});
+3
View File
@@ -71,6 +71,8 @@ module.exports = {
},
async fn(inputs) {
const { currentUser } = this.req;
if (sails.config.custom.oidcEnforced) {
throw Errors.NOT_ENOUGH_RIGHTS;
}
@@ -89,6 +91,7 @@ module.exports = {
const user = await sails.helpers.users.createOne
.with({
values,
actorUser: currentUser,
request: this.req,
})
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE)
+3
View File
@@ -26,6 +26,8 @@ module.exports = {
},
async fn(inputs) {
const { currentUser } = this.req;
let user = await sails.helpers.users.getOne(inputs.id);
if (!user) {
@@ -38,6 +40,7 @@ module.exports = {
user = await sails.helpers.users.deleteOne.with({
record: user,
actorUser: currentUser,
request: this.req,
});
@@ -91,7 +91,7 @@ module.exports = {
values: {
avatar: fileData,
},
user: currentUser,
actorUser: currentUser,
request: this.req,
});
+1 -1
View File
@@ -82,7 +82,7 @@ module.exports = {
.with({
values,
record: user,
user: currentUser,
actorUser: currentUser,
request: this.req,
})
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE);
@@ -80,7 +80,7 @@ module.exports = {
user = await sails.helpers.users.updateOne.with({
values,
record: user,
user: currentUser,
actorUser: currentUser,
request: this.req,
});
@@ -83,7 +83,7 @@ module.exports = {
.with({
values,
record: user,
user: currentUser,
actorUser: currentUser,
request: this.req,
})
.intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE);
+1 -1
View File
@@ -95,7 +95,7 @@ module.exports = {
user = await sails.helpers.users.updateOne.with({
values,
record: user,
user: currentUser,
actorUser: currentUser,
request: this.req,
});
+33 -9
View File
@@ -14,21 +14,21 @@ const valuesValidator = (value) => {
return true;
};
const buildAndSendSlackMessage = async (user, card, action) => {
const buildAndSendSlackMessage = async (card, action, actorUser) => {
const cardLink = `<${sails.config.custom.baseUrl}/cards/${card.id}|${card.name}>`;
let markdown;
switch (action.type) {
case Action.Types.CREATE_CARD:
markdown = `${cardLink} was created by ${user.name} in *${action.data.list.name}*`;
markdown = `${cardLink} was created by ${actorUser.name} in *${action.data.list.name}*`;
break;
case Action.Types.MOVE_CARD:
markdown = `${cardLink} was moved by ${user.name} to *${action.data.toList.name}*`;
markdown = `${cardLink} was moved by ${actorUser.name} to *${action.data.toList.name}*`;
break;
case Action.Types.COMMENT_CARD:
markdown = `*${user.name}* commented on ${cardLink}:\n>${action.data.text}`;
markdown = `*${actorUser.name}* commented on ${cardLink}:\n>${action.data.text}`;
break;
default:
@@ -45,10 +45,18 @@ module.exports = {
custom: valuesValidator,
required: true,
},
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
list: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -72,6 +80,24 @@ module.exports = {
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'actionCreate',
data: {
item: action,
included: {
projects: [inputs.project],
boards: [inputs.board],
lists: [inputs.list],
cards: [values.card],
},
},
user: values.user,
});
if (sails.config.custom.slackBotToken) {
buildAndSendSlackMessage(values.card, action, values.user);
}
const subscriptionUserIds = await sails.helpers.cards.getSubscriptionUserIds(
action.cardId,
action.userId,
@@ -84,17 +110,15 @@ module.exports = {
userId,
action,
},
user: values.user,
project: inputs.project,
board: inputs.board,
list: inputs.list,
card: values.card,
actorUser: values.user,
}),
),
);
if (sails.config.custom.slackBotToken) {
buildAndSendSlackMessage(values.user, values.card, action);
}
return action;
},
};
+30
View File
@@ -4,10 +4,26 @@ module.exports = {
type: 'ref',
required: true,
},
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
list: {
type: 'ref',
required: true,
},
card: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -25,6 +41,20 @@ module.exports = {
},
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'actionDelete',
data: {
item: action,
included: {
projects: [inputs.project],
boards: [inputs.board],
lists: [inputs.list],
cards: [inputs.card],
},
},
user: inputs.actorUser,
});
}
return action;
+30
View File
@@ -8,10 +8,26 @@ module.exports = {
type: 'json',
required: true,
},
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
list: {
type: 'ref',
required: true,
},
card: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -31,6 +47,20 @@ module.exports = {
},
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'actionUpdate',
data: {
item: action,
included: {
projects: [inputs.project],
boards: [inputs.board],
lists: [inputs.list],
cards: [inputs.card],
},
},
user: inputs.actorUser,
});
}
return action;
@@ -21,6 +21,18 @@ module.exports = {
custom: valuesValidator,
required: true,
},
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
list: {
type: 'ref',
required: true,
},
requestId: {
type: 'string',
isNotEmptyString: true,
@@ -49,12 +61,30 @@ module.exports = {
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'attachmentCreate',
data: {
item: attachment,
included: {
projects: [inputs.project],
boards: [inputs.board],
lists: [inputs.list],
cards: [values.card],
},
},
user: values.creatorUser,
});
if (!values.card.coverAttachmentId && attachment.image) {
await sails.helpers.cards.updateOne.with({
record: values.card,
values: {
coverAttachmentId: attachment.id,
},
project: inputs.project,
board: inputs.board,
list: inputs.list,
actorUser: values.creatorUser,
});
}
@@ -7,14 +7,26 @@ module.exports = {
type: 'ref',
required: true,
},
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
list: {
type: 'ref',
required: true,
},
card: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -27,6 +39,10 @@ module.exports = {
values: {
coverAttachmentId: null,
},
project: inputs.project,
board: inputs.board,
list: inputs.list,
actorUser: inputs.actorUser,
request: inputs.request,
});
}
@@ -48,6 +64,20 @@ module.exports = {
},
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'attachmentDelete',
data: {
item: attachment,
included: {
projects: [inputs.project],
boards: [inputs.board],
lists: [inputs.list],
cards: [inputs.card],
},
},
user: inputs.actorUser,
});
}
return attachment;
@@ -8,10 +8,26 @@ module.exports = {
type: 'json',
required: true,
},
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
list: {
type: 'ref',
required: true,
},
card: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -31,6 +47,20 @@ module.exports = {
},
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'attachmentUpdate',
data: {
item: attachment,
included: {
projects: [inputs.project],
boards: [inputs.board],
lists: [inputs.list],
cards: [inputs.card],
},
},
user: inputs.actorUser,
});
}
return attachment;
@@ -21,6 +21,14 @@ module.exports = {
custom: valuesValidator,
required: true,
},
project: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -67,6 +75,19 @@ module.exports = {
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'boardMembershipCreate',
data: {
item: boardMembership,
included: {
users: [values.user],
projects: [inputs.project],
boards: [values.board],
},
},
user: inputs.actorUser,
});
return boardMembership;
},
};
@@ -10,6 +10,14 @@ module.exports = {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -73,6 +81,18 @@ module.exports = {
);
});
}
sails.helpers.utils.sendWebhooks.with({
event: 'boardMembershipDelete',
data: {
item: boardMembership,
included: {
projects: [inputs.project],
boards: [inputs.board],
},
},
user: inputs.actorUser,
});
}
return boardMembership;
@@ -8,6 +8,18 @@ module.exports = {
type: 'json',
required: true,
},
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -40,6 +52,18 @@ module.exports = {
},
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'boardMembershipUpdate',
data: {
item: boardMembership,
included: {
projects: [inputs.project],
boards: [inputs.board],
},
},
user: inputs.actorUser,
});
}
return boardMembership;
+16 -3
View File
@@ -37,7 +37,7 @@ module.exports = {
type: 'json',
custom: importValidator,
},
user: {
actorUser: {
type: 'ref',
required: true,
},
@@ -80,6 +80,8 @@ module.exports = {
position: nextPosition,
},
});
// TODO: send webhooks
});
});
@@ -90,12 +92,12 @@ module.exports = {
}).fetch();
if (inputs.import && inputs.import.type === Board.ImportTypes.TRELLO) {
await sails.helpers.boards.importFromTrello(inputs.user, board, inputs.import.board);
await sails.helpers.boards.importFromTrello(board, inputs.import.board, inputs.actorUser);
}
const boardMembership = await BoardMembership.create({
boardId: board.id,
userId: inputs.user.id,
userId: inputs.actorUser.id,
role: BoardMembership.Roles.EDITOR,
}).fetch();
@@ -111,6 +113,17 @@ module.exports = {
);
});
sails.helpers.utils.sendWebhooks.with({
event: 'boardCreate',
data: {
item: board,
included: {
projects: [values.project],
},
},
user: inputs.actorUser,
});
return {
board,
boardMembership,
+19
View File
@@ -4,6 +4,14 @@ module.exports = {
type: 'ref',
required: true,
},
project: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -33,6 +41,17 @@ module.exports = {
inputs.request,
);
});
sails.helpers.utils.sendWebhooks.with({
event: 'boardDelete',
data: {
item: board,
included: {
projects: [inputs.project],
},
},
user: inputs.actorUser,
});
}
return board;
@@ -2,10 +2,6 @@ const POSITION_GAP = 65535; // TODO: move to config
module.exports = {
inputs: {
user: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
@@ -14,6 +10,10 @@ module.exports = {
type: 'json',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
},
async fn(inputs) {
@@ -87,7 +87,7 @@ module.exports = {
trelloComments.map(async (trelloComment) => {
return Action.create({
cardId: plankaCard.id,
userId: inputs.user.id,
userId: inputs.actorUser.id,
type: 'commentCard',
data: {
text:
@@ -105,7 +105,7 @@ module.exports = {
const plankaCard = await Card.create({
boardId: inputs.board.id,
listId: plankaList.id,
creatorUserId: inputs.user.id,
creatorUserId: inputs.actorUser.id,
position: trelloCard.pos,
name: trelloCard.name,
description: trelloCard.desc || null,
+21
View File
@@ -21,6 +21,14 @@ module.exports = {
custom: valuesValidator,
required: true,
},
project: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -64,6 +72,8 @@ module.exports = {
position: nextPosition,
},
});
// TODO: send webhooks
});
});
}
@@ -81,6 +91,17 @@ module.exports = {
inputs.request,
);
});
sails.helpers.utils.sendWebhooks.with({
event: 'boardUpdate',
data: {
item: board,
included: {
projects: [inputs.project],
},
},
user: inputs.actorUser,
});
}
return board;
@@ -21,6 +21,22 @@ module.exports = {
custom: valuesValidator,
required: true,
},
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
list: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -50,6 +66,21 @@ module.exports = {
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'cardLabelCreate',
data: {
item: cardLabel,
included: {
projects: [inputs.project],
boards: [inputs.board],
labels: [values.label],
lists: [inputs.list],
cards: [values.card],
},
},
user: inputs.actorUser,
});
return cardLabel;
},
};
@@ -4,10 +4,26 @@ module.exports = {
type: 'ref',
required: true,
},
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
list: {
type: 'ref',
required: true,
},
card: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -25,6 +41,20 @@ module.exports = {
},
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'cardLabelDelete',
data: {
item: cardLabel,
included: {
projects: [inputs.project],
boards: [inputs.board],
lists: [inputs.list],
cards: [inputs.card],
},
},
user: inputs.actorUser,
});
}
return cardLabel;
@@ -21,6 +21,22 @@ module.exports = {
custom: valuesValidator,
required: true,
},
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
list: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -53,6 +69,20 @@ module.exports = {
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'cardMembershipCreate',
data: {
item: cardMembership,
included: {
projects: [inputs.project],
boards: [inputs.board],
lists: [inputs.list],
cards: [values.card],
},
},
user: inputs.actorUser,
});
const cardSubscription = await CardSubscription.create({
cardId: cardMembership.cardId,
userId: cardMembership.userId,
@@ -73,6 +103,8 @@ module.exports = {
},
inputs.request,
);
// TODO: send webhooks
}
return cardMembership;
@@ -4,10 +4,26 @@ module.exports = {
type: 'ref',
required: true,
},
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
list: {
type: 'ref',
required: true,
},
card: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: {
type: 'ref',
},
@@ -26,6 +42,20 @@ module.exports = {
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'cardMembershipDelete',
data: {
item: cardMembership,
included: {
projects: [inputs.project],
boards: [inputs.board],
lists: [inputs.list],
cards: [inputs.card],
},
},
user: inputs.actorUser,
});
const cardSubscription = await CardSubscription.destroyOne({
cardId: cardMembership.cardId,
userId: cardMembership.userId,
@@ -39,6 +69,8 @@ module.exports = {
isSubscribed: false,
},
});
// TODO: send webhook
}
}
+24
View File
@@ -25,6 +25,10 @@ module.exports = {
custom: valuesValidator,
required: true,
},
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
@@ -66,6 +70,8 @@ module.exports = {
position: nextPosition,
},
});
// TODO: send webhooks
});
const card = await Card.create({
@@ -85,6 +91,19 @@ module.exports = {
inputs.request,
);
sails.helpers.utils.sendWebhooks.with({
event: 'cardCreate',
data: {
item: card,
included: {
projects: [inputs.project],
boards: [inputs.board],
lists: [values.list],
},
},
user: values.creatorUser,
});
if (values.creatorUser.subscribeToOwnCards) {
await CardSubscription.create({
cardId: card.id,
@@ -97,6 +116,8 @@ module.exports = {
isSubscribed: true,
},
});
// TODO: send webhooks
}
await sails.helpers.actions.createOne.with({
@@ -108,7 +129,10 @@ module.exports = {
},
user: values.creatorUser,
},
project: inputs.project,
board: inputs.board,
list: values.list,
request: inputs.request,
});
return card;

Some files were not shown because too many files have changed in this diff Show More