Compare commits

..
29 Commits
Author SHA1 Message Date
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
Maksim Eltyshev 4cbd2f85c7 chore: Update version 2024-05-16 21:06:30 +02:00
Maksim Eltyshev fae9de4e1c fix: Prevent loading fonts from third-party resources
Closes #538, closes #712, closes #742, closes #755
2024-05-16 21:02:58 +02:00
IT Creativity + Art Team 89c1ed71e1 feat: Add Bulgarian translation (#753) 2024-05-13 13:05:20 +02:00
Mitch Harvey 1725f3cb8a feat: Ability to specify existing secret as database url (#751)
Closes #750
2024-05-13 11:07:57 +02:00
Christopher Greaves f7be49e402 docs: Update Kubernetes README (#740) 2024-05-03 21:04:34 +02:00
139 changed files with 8279 additions and 2097 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 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 # 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. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.29 version: 0.2.2
# This is the version number of the application being deployed. This version number should be # 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 # 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. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "1.17.2" appVersion: "1.19.1"
dependencies: dependencies:
- alias: postgresql - alias: postgresql
+6 -2
View File
@@ -14,11 +14,15 @@ If you want to fully uninstall this chart including the data, follow [these step
## Usage ## Usage
If you just want to spin up an instance using help, please see [these docs](https://docs.planka.cloud/docs/installation/kubernetes/helm_chart/). If you want to make changes to the chart locally, and deploy them, see the below section.
## Local Building and Using the Chart
The basic usage of the chart can be found below: The basic usage of the chart can be found below:
```bash ```bash
git clone https://github.com/Chris-Greaves/planka-helm-chart.git git clone https://github.com/plankanban/planka.git
cd planka-helm-chart cd planka/charts/planka
helm dependency build helm dependency build
export SECRETKEY=$(openssl rand -hex 64) export SECRETKEY=$(openssl rand -hex 64)
helm install planka . --set secretkey=$SECRETKEY \ helm install planka . --set secretkey=$SECRETKEY \
+31 -3
View File
@@ -64,8 +64,16 @@ spec:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.resources | nindent 12 }}
env: env:
{{- if not .Values.postgresql.enabled }} {{- if not .Values.postgresql.enabled }}
{{- if .Values.existingDburlSecret }}
- name: DATABASE_URL - name: DATABASE_URL
value: {{ required "If the included postgresql deployment is disabled you need to define a Database URL in 'dburl'" .Values.dburl }} valueFrom:
secretKeyRef:
name: {{ .Values.existingDburlSecret }}
key: uri
{{- else }}
- name: DATABASE_URL
value: {{ required "If the included postgresql deployment is disabled you need to provide an existing secret in .Values.existingDburlSecret or define a Database URL in 'dburl'" .Values.dburl }}
{{- end }}
{{- else }} {{- else }}
- name: DATABASE_URL - name: DATABASE_URL
valueFrom: valueFrom:
@@ -82,17 +90,37 @@ spec:
value: http://localhost:3000 value: http://localhost:3000
{{- end }} {{- end }}
- name: SECRET_KEY - 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 }} value: {{ required "A secret key needs to be generated using 'openssl rand -hex 64' and assigned to secretkey." .Values.secretkey }}
{{- end }}
- name: TRUST_PROXY - name: TRUST_PROXY
value: "0" value: "0"
- name: DEFAULT_ADMIN_EMAIL - name: DEFAULT_ADMIN_EMAIL
value: {{ .Values.admin_email }} value: {{ .Values.admin_email }}
- name: DEFAULT_ADMIN_PASSWORD
value: {{ .Values.admin_password }}
- name: DEFAULT_ADMIN_NAME - name: DEFAULT_ADMIN_NAME
value: {{ .Values.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 - name: DEFAULT_ADMIN_USERNAME
value: {{ .Values.admin_username }} value: {{ .Values.admin_username }}
- name: DEFAULT_ADMIN_PASSWORD
value: {{ .Values.admin_password }}
{{- end }}
{{ range $k, $v := .Values.env }} {{ range $k, $v := .Values.env }}
- name: {{ $k | quote }} - name: {{ $k | quote }}
value: {{ $v | quote }} value: {{ $v | quote }}
+17 -1
View File
@@ -17,6 +17,16 @@ fullnameOverride: ""
# Generate a secret using openssl rand -base64 45 # Generate a secret using openssl rand -base64 45
secretkey: "" 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` # Base url for Planka. Will override `ingress.hosts[0].host`
# Defaults to `http://localhost:3000` if ingress is disabled. # Defaults to `http://localhost:3000` if ingress is disabled.
baseUrl: "" baseUrl: ""
@@ -105,9 +115,15 @@ postgresql:
serviceBindings: serviceBindings:
enabled: true enabled: true
## Set this if you disable the built-in postgresql deployment ## Set this or existingDburlSecret if you disable the built-in postgresql deployment
dburl: dburl:
## @param existingDburlSecret Name of an existing secret containing a DBurl connection string
## NOTE: Must contain key `uri`
## NOTE: When it's set, the `dburl` parameter is ignored
##
existingDburlSecret: ""
## PVC-based data storage configuration ## PVC-based data storage configuration
persistence: persistence:
enabled: false enabled: false
+1 -1
View File
@@ -1 +1 @@
REACT_APP_VERSION=1.17.2 REACT_APP_VERSION=1.19.1
+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", "dequal": "^2.0.3",
"easymde": "^2.18.0", "easymde": "^2.18.0",
"history": "^5.3.0", "history": "^5.3.0",
"i18next": "^23.11.2", "i18next": "^23.11.5",
"i18next-browser-languagedetector": "^7.2.1", "i18next-browser-languagedetector": "^8.0.0",
"initials": "^3.1.2", "initials": "^3.1.2",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
@@ -75,7 +75,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"node-sass": "^9.0.0", "node-sass": "^9.0.0",
"photoswipe": "^5.4.3", "photoswipe": "^5.4.4",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-app-rewired": "^2.2.1", "react-app-rewired": "^2.2.1",
@@ -83,12 +83,12 @@
"react-datepicker": "^4.25.0", "react-datepicker": "^4.25.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-i18next": "^13.5.0", "react-i18next": "^14.1.2",
"react-input-mask": "^2.0.4", "react-input-mask": "^2.0.4",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"react-photoswipe-gallery": "^2.2.7", "react-photoswipe-gallery": "^2.2.7",
"react-redux": "^8.1.3", "react-redux": "^8.1.3",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.23.1",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-simplemde-editor": "^5.2.0", "react-simplemde-editor": "^5.2.0",
"react-textarea-autosize": "^8.5.3", "react-textarea-autosize": "^8.5.3",
@@ -102,13 +102,13 @@
"sails.io.js": "^1.2.1", "sails.io.js": "^1.2.1",
"semantic-ui-react": "^2.1.5", "semantic-ui-react": "^2.1.5",
"socket.io-client": "^2.5.0", "socket.io-client": "^2.5.0",
"validator": "^13.11.0", "validator": "^13.12.0",
"whatwg-fetch": "^3.6.20", "whatwg-fetch": "^3.6.20",
"zxcvbn": "^4.4.2" "zxcvbn": "^4.4.2"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^6.4.2", "@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^14.3.1", "@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"babel-preset-airbnb": "^5.0.0", "babel-preset-airbnb": "^5.0.0",
"chai": "^4.4.1", "chai": "^4.4.1",
@@ -116,8 +116,8 @@
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.34.1", "eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.2",
"react-test-renderer": "^18.2.0" "react-test-renderer": "^18.2.0"
} }
} }
+1 -1
View File
@@ -13,7 +13,7 @@ io.sails.environment = process.env.NODE_ENV;
const { socket } = io; 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 socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => { ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -1
View File
@@ -5,6 +5,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react'; import { Button, Form, TextArea } from 'semantic-ui-react';
import { useClosableForm, useField } from '../../hooks'; import { useClosableForm, useField } from '../../hooks';
import { focusEnd } from '../../utils/element-helpers';
import styles from './NameEdit.module.scss'; import styles from './NameEdit.module.scss';
@@ -79,7 +80,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
field.current.ref.current.focus(); focusEnd(field.current.ref.current);
} }
}, [isOpened]); }, [isOpened]);
@@ -6,6 +6,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react'; import { Button, Form, TextArea } from 'semantic-ui-react';
import { useForm } from '../../../hooks'; import { useForm } from '../../../hooks';
import { focusEnd } from '../../../utils/element-helpers';
import styles from './CommentEdit.module.scss'; import styles from './CommentEdit.module.scss';
@@ -70,7 +71,7 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
textField.current.ref.current.focus(); focusEnd(textField.current.ref.current);
} }
}, [isOpened]); }, [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 PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -81,6 +81,7 @@ const CardModal = React.memo(
onClose, onClose,
}) => { }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const [isLinkCopied, setIsLinkCopied] = useState(false);
const isGalleryOpened = useRef(false); const isGalleryOpened = useRef(false);
@@ -146,6 +147,14 @@ const CardModal = React.memo(
onClose(); onClose();
}, [onDuplicate, onClose]); }, [onDuplicate, onClose]);
const handleCopyLinkClick = useCallback(() => {
navigator.clipboard.writeText(window.location.href);
setIsLinkCopied(true);
setTimeout(() => {
setIsLinkCopied(false);
}, 5000);
}, []);
const handleGalleryOpen = useCallback(() => { const handleGalleryOpen = useCallback(() => {
isGalleryOpened.current = true; isGalleryOpened.current = true;
}, []); }, []);
@@ -506,6 +515,14 @@ const CardModal = React.memo(
<Icon name="copy outline" className={styles.actionIcon} /> <Icon name="copy outline" className={styles.actionIcon} />
{t('action.duplicate')} {t('action.duplicate')}
</Button> </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 <DeletePopup
title="common.deleteCard" title="common.deleteCard"
content="common.areYouSureYouWantToDeleteThisCard" content="common.areYouSureYouWantToDeleteThisCard"
@@ -20,7 +20,6 @@
.actionIcon { .actionIcon {
color: #17394d; color: #17394d;
display: inline;
margin-right: 8px; margin-right: 8px;
} }
@@ -57,6 +57,7 @@ const DescriptionEdit = React.forwardRef(({ children, defaultValue, onUpdate },
const mdEditorOptions = useMemo( const mdEditorOptions = useMemo(
() => ({ () => ({
autoDownloadFontAwesome: false,
autofocus: true, autofocus: true,
spellChecker: false, spellChecker: false,
status: false, status: false,
@@ -5,6 +5,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react'; import { Button, Form, TextArea } from 'semantic-ui-react';
import { useField } from '../../../hooks'; import { useField } from '../../../hooks';
import { focusEnd } from '../../../utils/element-helpers';
import styles from './NameEdit.module.scss'; import styles from './NameEdit.module.scss';
@@ -65,7 +66,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
field.current.ref.current.focus(); focusEnd(field.current.ref.current);
} }
}, [isOpened]); }, [isOpened]);
+2 -1
View File
@@ -4,6 +4,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { TextArea } from 'semantic-ui-react'; import { TextArea } from 'semantic-ui-react';
import { useField } from '../../hooks'; import { useField } from '../../hooks';
import { focusEnd } from '../../utils/element-helpers';
import styles from './NameEdit.module.scss'; import styles from './NameEdit.module.scss';
@@ -71,7 +72,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
field.current.ref.current.select(); focusEnd(field.current.ref.current);
} }
}, [isOpened]); }, [isOpened]);
+1
View File
@@ -13,6 +13,7 @@ import 'react-datepicker/dist/react-datepicker.css';
import 'photoswipe/dist/photoswipe.css'; import 'photoswipe/dist/photoswipe.css';
import 'easymde/dist/easymde.min.css'; import 'easymde/dist/easymde.min.css';
import '../lib/custom-ui/styles.css'; import '../lib/custom-ui/styles.css';
import '../assets/css/font-awesome.css';
import '../styles.module.scss'; import '../styles.module.scss';
function Root({ store, history }) { function Root({ store, history }) {
+2
View File
@@ -6,6 +6,7 @@ const BASE_PATH = BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
const SERVER_BASE_URL = const SERVER_BASE_URL =
process.env.REACT_APP_SERVER_BASE_URL || process.env.REACT_APP_SERVER_BASE_URL ||
(process.env.NODE_ENV === 'production' ? BASE_URL : 'http://localhost:1337'); (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'); const SERVER_HOST_NAME = SERVER_BASE_URL.replace(/^(.*\/\/[^/?#]*).*$/, '$1');
@@ -20,6 +21,7 @@ export default {
VERSION, VERSION,
BASE_PATH, BASE_PATH,
SERVER_BASE_URL, SERVER_BASE_URL,
SERVER_BASE_PATH,
SERVER_HOST_NAME, SERVER_HOST_NAME,
ACCESS_TOKEN_KEY, ACCESS_TOKEN_KEY,
ACCESS_TOKEN_VERSION_KEY, ACCESS_TOKEN_VERSION_KEY,
+2 -1
View File
@@ -6,12 +6,13 @@ import entryActions from '../entry-actions';
import Projects from '../components/Projects'; import Projects from '../components/Projects';
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const { allowAllToCreateProjects } = selectors.selectConfig(state);
const { isAdmin } = selectors.selectCurrentUser(state); const { isAdmin } = selectors.selectCurrentUser(state);
const projects = selectors.selectProjectsForCurrentUser(state); const projects = selectors.selectProjectsForCurrentUser(state);
return { return {
items: projects, items: projects,
canAdd: isAdmin, canAdd: allowAllToCreateProjects || isAdmin,
}; };
}; };
+252
View File
@@ -0,0 +1,252 @@
import dateFns from 'date-fns/locale/bg';
export default {
dateFns,
format: {
date: 'd.MM.yyyy',
time: 'p',
dateTime: '$t(format:date) $t(format:time)',
longDate: 'd. MMM',
longDateTime: "d. MMMM 'в' p",
fullDate: 'd. MMM. y',
fullDateTime: "d. MMMM. y 'в' p",
},
translation: {
common: {
aboutPlanka: 'За Planka',
account: 'Акаунт',
actions: 'Действия',
addAttachment_title: 'Добавяне на прикачен файл',
addComment: 'Добавяне на коментар',
addManager_title: 'Добавяне на мениджър',
addMember_title: 'Добавяне на член',
addUser_title: 'Добавяне на потребител',
administrator: 'Администратор',
all: 'Всички',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'Всички промени ще бъдат автоматично запазени<br />след възстановяване на връзката.',
areYouSureYouWantToDeleteThisAttachment:
'Сигурни ли сте, че искате да изтриете този прикачен файл?',
areYouSureYouWantToDeleteThisBoard: 'Сигурни ли сте, че искате да изтриете това табло?',
areYouSureYouWantToDeleteThisCard: 'Сигурни ли сте, че искате да изтриете тази карта?',
areYouSureYouWantToDeleteThisComment: 'Сигурни ли сте, че искате да изтриете този коментар?',
areYouSureYouWantToDeleteThisLabel: 'Сигурни ли сте, че искате да изтриете този етикет?',
areYouSureYouWantToDeleteThisList: 'Сигурни ли сте, че искате да изтриете този списък?',
areYouSureYouWantToDeleteThisProject: 'Сигурни ли сте, че искате да изтриете този проект?',
areYouSureYouWantToDeleteThisTask: 'Сигурни ли сте, че искате да изтриете тази задача?',
areYouSureYouWantToDeleteThisUser: 'Сигурни ли сте, че искате да изтриете този потребител?',
areYouSureYouWantToLeaveBoard: 'Сигурни ли сте, че искате да напуснете таблото?',
areYouSureYouWantToLeaveProject: 'Сигурни ли сте, че искате да напуснете проекта?',
areYouSureYouWantToRemoveThisManagerFromProject:
'Сигурни ли сте, че искате да премахнете този мениджър от проекта?',
areYouSureYouWantToRemoveThisMemberFromBoard:
'Сигурни ли сте, че искате да премахнете този член от таблото?',
attachment: 'Прикачен файл',
attachments: 'Прикачени файлове',
authentication: 'Удостоверяване',
background: 'Фон',
board: 'Табло',
boardNotFound_title: 'Таблото не е намерено',
canComment: 'може да коментира',
canEditContentOfBoard: 'може да редактира съдържание на таблото',
canOnlyViewBoard: 'може само да преглежда таблото',
cardActions_title: 'Действия с карта',
cardNotFound_title: 'Картата не може да бъде открита',
cardOrActionAreDeleted: 'Картата или действието са изтрити.',
color: 'Цвят',
copy_inline: 'копирай',
createBoard_title: 'Създаване на табло',
createLabel_title: 'Създаване на етикет',
createNewOneOrSelectExistingOne: 'Създайте нов или изберете<br/>съществуващ.',
createProject_title: 'Създаване на проект',
createTextFile_title: 'Създаване на текстов файл',
currentPassword: 'Текуща парола',
dangerZone_title: 'Опасна зона',
date: 'Дата',
dueDate: 'Дата на падежа',
dueDate_title: 'Краен срок',
deleteAttachment_title: 'Изтриване на прикачен файл',
deleteBoard_title: 'Изтриване на табло',
deleteCard_title: 'Изтриване на карта',
deleteComment_title: 'Изтриване на коментар',
deleteLabel_title: 'Изтриване на етикета',
deleteList_title: 'Изтриване на списък',
deleteProject_title: 'Изтриване на проект',
deleteTask_title: 'Изтриване на задача',
deleteUser_title: 'Изтриване на потребител',
description: 'Описание',
detectAutomatically: 'Aвтоматично откриване',
dropFileToUpload: 'Пуснете файл за качване',
editor: 'Редактор',
editAttachment_title: 'Редактирай прикачения файл',
editAvatar_title: 'Редактиране на аватар',
editBoard_title: 'Редактиране на табло',
editDueDate_title: 'Редактиране на дата на падеж',
editEmail_title: 'Редактиране на имейл',
editInformation_title: 'Редактиране на информация',
editLabel_title: 'Редактиране на табло',
editPassword_title: 'Редактиране на парола',
editPermissions_title: 'Редактиране на права',
editStopwatch_title: 'Редактиране на Хронометър',
editUsername_title: 'Редактиране на потребителско име',
email: 'Имейл',
emailAlreadyInUse: 'Имейлът вече се използва',
enterCardTitle: 'Въведете заглавие на картата... [Ctrl+Enter] за автоматично отваряне.',
enterDescription: 'Въведете описание...',
enterFilename: 'Въведете име на файла',
enterListTitle: 'Въведете заглавие на списък...',
enterProjectTitle: 'Въведете заглавие на проекта',
enterTaskDescription: 'Въведете описание на задачата...',
filterByLabels_title: 'Филтриране по етикети',
filterByMembers_title: 'Филтриране по членове',
fromComputer_title: 'От компютър',
fromTrello: 'От Trello',
general: 'Общ',
hours: 'Часове',
importBoard_title: 'Импортиране на табло',
invalidCurrentPassword: 'Невалидна текуща парола',
labels: 'Етикети',
language: 'Език',
leaveBoard_title: 'Напуснете таблото',
leaveProject_title: 'Напуснете проекта',
list: 'Списък',
listActions_title: 'Списък с действия',
managers: 'Мениджъри',
managerActions_title: 'Действия с мениджъри',
members: 'Членове',
memberActions_title: 'Действия с членове',
minutes: 'Минути',
moveCard_title: 'Преместване на карта',
name: 'Име',
newestFirst: 'Първо най-новите',
newEmail: 'Нов имейл',
newPassword: 'Нова парола',
newUsername: 'Ново потребителско име',
noConnectionToServer: 'Няма връзка със сървъра',
noBoards: 'Няма табла',
noLists: 'Няма списъци',
noProjects: 'Няма проекти',
notifications: 'Уведомления',
noUnreadNotifications: 'Няма непрочетени известия.',
oldestFirst: 'Първо най-старите',
openBoard_title: 'Отворете табло',
optional_inline: 'по желание',
organization: 'Организация',
phone: 'Телефон',
preferences: 'Предпочитания',
pressPasteShortcutToAddAttachmentFromClipboard:
'Съвет: натиснете Ctrl-V (Cmd-V на Mac), за да добавите прикачен файл от клипборда',
project: 'Проект',
projectNotFound_title: 'Проектът не е намерен',
removeManager_title: 'Премахване на мениджър',
removeMember_title: 'Премахване на член',
searchLabels: 'Търсене на етикети...',
searchMembers: 'Търсене на членове...',
searchUsers: 'Търсене на потребители...',
searchCards: 'Търсене на карти...',
seconds: 'секунди',
selectBoard: 'Изберете табло',
selectList: 'Изберете списък',
selectPermissions_title: 'Изберете правата',
selectProject: 'Изберете проект',
settings: 'Настройки',
sortList_title: 'Сортиране на списък',
stopwatch: 'Хронометър',
subscribeToMyOwnCardsByDefault: 'Абонирайте се за собствените си карти по подразбиране',
taskActions_title: 'Действия със задачи',
tasks: 'Задачи',
thereIsNoPreviewAvailableForThisAttachment: 'Няма наличен преглед за този прикачен файл.',
time: 'Време',
title: 'Заглавие',
userActions_title: 'Потребителски действия',
userAddedThisCardToList: '<0>{{user}}</0><1> добави тази карта в {{list}}</1>',
userLeftNewCommentToCard: '{{user}} остави нов коментар «{{comment}}» в <2>{{card}}</2>',
userMovedCardFromListToList:
'{{user}} премести <2>{{card}}</2> от {{fromList}} към {{toList}}',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> премести тази карта от {{fromList}} към {{toList}}</1>',
username: 'Потребителско име',
usernameAlreadyInUse: 'Потребителското име вече се използва',
users: 'Потребители',
version: 'Версия',
viewer: 'Зрител',
writeComment: 'Напишете коментар...',
},
action: {
addAnotherCard: 'Добавяне на друга карта',
addAnotherList: 'Добавяне на друг списък',
addAnotherTask: 'Добавяне на друга задача',
addCard: 'Добавяне на карта',
addCard_title: 'Добавяне на карта',
addComment: 'Добавяне на коментар',
addList: 'Добавяне на списък',
addMember: 'Добавяне на член',
addMoreDetailedDescription: 'Добавяне на по-подробно описание',
addTask: 'Добавяне на задача',
addToCard: 'Добавяне към карта',
addUser: 'Добавяне на потребител',
createBoard: 'Създаване на табло',
createFile: 'Създаване на файл',
createLabel: 'Създаване на етикет',
createNewLabel: 'Създаване на нов етикет',
createProject: 'Създаване на проект',
delete: 'Изтриване',
deleteAttachment: 'Изтриване на прикачения файл',
deleteAvatar: 'Изтриване на аватар',
deleteBoard: 'Изтриване на табло',
deleteCard: 'Изтриване на карта',
deleteCard_title: 'Изтриване на карта',
deleteComment: 'Изтриване на коментара',
deleteImage: 'Изтриване на изображение',
deleteLabel: 'Изтриване на етикета',
deleteList: 'Изтриване на списък',
deleteList_title: 'Изтриване на списък',
deleteProject: 'Изтриване на проект',
deleteProject_title: 'Изтриване на проект',
deleteTask: 'Изтриване на задача',
deleteTask_title: 'Изтриване на задача',
deleteUser: 'Изтриване на потребител',
duplicate: 'Направи дубликат',
duplicateCard_title: 'Дублирана карта',
edit: 'Редактиране',
editDueDate_title: 'Редактиране на краен срок',
editDescription_title: 'Редактиране на описание',
editEmail_title: 'Редактиране на имейл',
editInformation_title: 'Редактиране на информация',
editPassword_title: 'Редактиране на парола',
editPermissions: 'Разрешения за редактиране',
editStopwatch_title: 'Редактиране на хронометър',
editTitle_title: 'Редактиране на заглавието',
editUsername_title: 'Редактиране на потребителско име',
hideDetails: 'Скриване на детайлите',
import: 'Импортиране',
leaveBoard: 'Напускане на дъската',
leaveProject: 'Напускане на проекта',
logOut_title: 'Излезте',
makeCover_title: 'Създаване на корица',
move: 'Преместване',
moveCard_title: 'Преместване на карта',
remove: 'Премахване',
removeBackground: 'Премахване на фона',
removeCover_title: 'Премахване на корицата',
removeFromBoard: 'Премахване от борда',
removeFromProject: 'Премахване от проекта',
removeManager: 'Премахване на мениджър',
removeMember: 'Премахване на член',
save: 'Запазване',
showAllAttachments: 'Показване на всички прикачени файлове ({{hidden}} скрити)',
showDetails: 'Показване на подробности',
showFewerAttachments: 'Показване на по-малко прикачени файлове',
sortList_title: 'Списък за сортиране',
start: 'Старт',
stop: 'Стоп',
subscribe: 'Абонирайте се',
unsubscribe: 'Отписване',
uploadNewAvatar: 'Качване на нов аватар',
uploadNewImage: 'Качване на ново изображение',
},
},
};
+8
View File
@@ -0,0 +1,8 @@
import login from './login';
export default {
language: 'bg',
country: 'bg',
name: 'Български',
embeddedLocale: login,
};
+22
View File
@@ -0,0 +1,22 @@
export default {
translation: {
common: {
emailOrUsername: 'Имейл или потребителско име',
invalidEmailOrUsername: 'Невалиден имейл или потребителско име',
invalidPassword: 'Невалидна парола',
logInToPlanka: 'Влезте в Planka',
noInternetConnection: 'Няма интернет връзка',
pageNotFound_title: 'Страницата не е намерена',
password: 'Парола',
projectManagement: 'Управление на проекти',
serverConnectionFailed: 'Неуспешна връзка със сървъра',
unknownError: 'Неизвестна грешка, опитайте отново по-късно',
useSingleSignOn: 'Използване на single sign-on',
},
action: {
logIn: 'Вход',
logInWithSSO: 'Вход чрез SSO',
},
},
};
+2
View File
@@ -104,6 +104,7 @@ export default {
language: 'Sprache', language: 'Sprache',
leaveBoard_title: 'Board verlassen', leaveBoard_title: 'Board verlassen',
leaveProject_title: 'Projekt verlassen', leaveProject_title: 'Projekt verlassen',
linkIsCopied: 'Link kopiert',
list: 'Listen', list: 'Listen',
listActions_title: 'Aufgaben auflisten', listActions_title: 'Aufgaben auflisten',
managers: 'Manager', managers: 'Manager',
@@ -169,6 +170,7 @@ export default {
addTask: 'Aufgabe hinzufügen', addTask: 'Aufgabe hinzufügen',
addToCard: 'Zu Karte hinzufügen', addToCard: 'Zu Karte hinzufügen',
addUser: 'Benutzer hinzufügen', addUser: 'Benutzer hinzufügen',
copyLink_title: 'Link kopieren',
createBoard: 'Board erstellen', createBoard: 'Board erstellen',
createFile: 'Datei erstellen', createFile: 'Datei erstellen',
createLabel: 'Label erstellen', createLabel: 'Label erstellen',
+2
View File
@@ -106,6 +106,7 @@ export default {
language: 'Language', language: 'Language',
leaveBoard_title: 'Leave Board', leaveBoard_title: 'Leave Board',
leaveProject_title: 'Leave Project', leaveProject_title: 'Leave Project',
linkIsCopied: 'Link is copied',
list: 'List', list: 'List',
listActions_title: 'List Actions', listActions_title: 'List Actions',
managers: 'Managers', managers: 'Managers',
@@ -183,6 +184,7 @@ export default {
addTask: 'Add task', addTask: 'Add task',
addToCard: 'Add to card', addToCard: 'Add to card',
addUser: 'Add user', addUser: 'Add user',
copyLink_title: 'Copy Link',
createBoard: 'Create board', createBoard: 'Create board',
createFile: 'Create file', createFile: 'Create file',
createLabel: 'Create label', createLabel: 'Create label',
+2
View File
@@ -1,3 +1,4 @@
import bg from './bg';
import cs from './cs'; import cs from './cs';
import da from './da'; import da from './da';
import de from './de'; import de from './de';
@@ -22,6 +23,7 @@ import uz from './uz';
import zh from './zh'; import zh from './zh';
const locales = [ const locales = [
bg,
cs, cs,
da, da,
de, de,
+2
View File
@@ -142,6 +142,7 @@ export default {
searchLabels: 'Поиск меток...', searchLabels: 'Поиск меток...',
searchMembers: 'Поиск участников...', searchMembers: 'Поиск участников...',
searchUsers: 'Поиск пользователей...', searchUsers: 'Поиск пользователей...',
searchCards: 'Поиск карточек...',
seconds: 'Секунды', seconds: 'Секунды',
selectBoard: 'Выберите доску', selectBoard: 'Выберите доску',
selectList: 'Выберите список', selectList: 'Выберите список',
@@ -232,6 +233,7 @@ export default {
showAllAttachments: 'Показать все вложения ({{hidden}} скрыто)', showAllAttachments: 'Показать все вложения ({{hidden}} скрыто)',
showDetails: 'Показать подробности', showDetails: 'Показать подробности',
showFewerAttachments: 'Показать меньше вложений', showFewerAttachments: 'Показать меньше вложений',
sortList_title: 'Сортировать список',
start: 'Начать', start: 'Начать',
stop: 'Остановить', stop: 'Остановить',
subscribe: 'Подписаться', subscribe: 'Подписаться',
+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' version: '3.8'
services: services:
server: server:
build: build:
context: ./server context: ./server
@@ -13,6 +12,7 @@ services:
- NODE_ENV=development - NODE_ENV=development
- DATABASE_URL=postgresql://user:password@postgres:5432/planka_db - DATABASE_URL=postgresql://user:password@postgres:5432/planka_db
- SECRET_KEY=notsecretkey - SECRET_KEY=notsecretkey
# - TRUST_PROXY=0 # - TRUST_PROXY=0
# - TOKEN_EXPIRES_IN=365 # In days # - TOKEN_EXPIRES_IN=365 # In days
@@ -23,6 +23,9 @@ services:
# Configure knex to accept SSL certificates # Configure knex to accept SSL certificates
# - KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE=false # - KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE=false
# - ALLOW_ALL_TO_CREATE_PROJECTS=true
# - OIDC_ISSUER= # - OIDC_ISSUER=
# - OIDC_CLIENT_ID= # - OIDC_CLIENT_ID=
# - OIDC_CLIENT_SECRET= # - OIDC_CLIENT_SECRET=
@@ -39,14 +42,23 @@ services:
# Email Notifications (https://nodemailer.com/smtp/) # Email Notifications (https://nodemailer.com/smtp/)
# - SMTP_HOST= # - SMTP_HOST=
# - SMTP_PORT=587 # - SMTP_PORT=587
# - SMTP_NAME=
# - SMTP_SECURE=true # - SMTP_SECURE=true
# - SMTP_USER= # - SMTP_USER=
# - SMTP_PASSWORD= # - SMTP_PASSWORD=
# - SMTP_FROM="Demo Demo" <demo@demo.demo> # - 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_BOT_TOKEN=
# - SLACK_CHANNEL_ID= # - SLACK_CHANNEL_ID=
working_dir: /app working_dir: /app
command: ["sh", "-c", "npm run start"] command: ["sh", "-c", "npm run start"]
depends_on: depends_on:
@@ -75,11 +87,11 @@ services:
dockerfile: ../config/development/Dockerfile.server dockerfile: ../config/development/Dockerfile.server
environment: environment:
- DATABASE_URL=postgresql://user:password@postgres:5432/planka_db - 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_EMAIL=demo@demo.demo # Do not remove if you want to prevent this user from being edited/deleted
# - DEFAULT_ADMIN_PASSWORD=demo # - DEFAULT_ADMIN_PASSWORD=demo
# - DEFAULT_ADMIN_NAME=Demo Demo # - DEFAULT_ADMIN_NAME=Demo Demo
# - DEFAULT_ADMIN_USERNAME=demo # - DEFAULT_ADMIN_USERNAME=demo
working_dir: /app working_dir: /app
command: ["sh", "-c", "npm run db:init"] command: ["sh", "-c", "npm run db:init"]
volumes: volumes:
@@ -116,6 +128,5 @@ services:
- server - server
- client - client
volumes: volumes:
db-data: db-data:
+12
View File
@@ -31,6 +31,8 @@ services:
# - DEFAULT_ADMIN_NAME=Demo Demo # - DEFAULT_ADMIN_NAME=Demo Demo
# - DEFAULT_ADMIN_USERNAME=demo # - DEFAULT_ADMIN_USERNAME=demo
# - ALLOW_ALL_TO_CREATE_PROJECTS=true
# - OIDC_ISSUER= # - OIDC_ISSUER=
# - OIDC_CLIENT_ID= # - OIDC_CLIENT_ID=
# - OIDC_CLIENT_SECRET= # - OIDC_CLIENT_SECRET=
@@ -47,11 +49,21 @@ services:
# Email Notifications (https://nodemailer.com/smtp/) # Email Notifications (https://nodemailer.com/smtp/)
# - SMTP_HOST= # - SMTP_HOST=
# - SMTP_PORT=587 # - SMTP_PORT=587
# - SMTP_NAME=
# - SMTP_SECURE=true # - SMTP_SECURE=true
# - SMTP_USER= # - SMTP_USER=
# - SMTP_PASSWORD= # - SMTP_PASSWORD=
# - SMTP_FROM="Demo Demo" <demo@demo.demo> # - 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_BOT_TOKEN=
# - SLACK_CHANNEL_ID= # - SLACK_CHANNEL_ID=
depends_on: depends_on:
+80 -71
View File
@@ -1,39 +1,30 @@
{ {
"name": "planka", "name": "planka",
"version": "1.17.2", "version": "1.19.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "planka", "name": "planka",
"version": "1.17.2", "version": "1.19.1",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"husky": "^8.0.3", "husky": "^9.0.1",
"lint-staged": "^15.2.2" "lint-staged": "^15.2.5"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"prettier": "^3.2.5" "prettier": "^3.3.0"
}
},
"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"
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.24.4", "version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz",
"integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
}, },
@@ -276,11 +267,11 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -427,11 +418,11 @@
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="
}, },
"node_modules/commander": { "node_modules/commander": {
"version": "11.1.0", "version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"engines": { "engines": {
"node": ">=16" "node": ">=18"
} }
}, },
"node_modules/concat-map": { "node_modules/concat-map": {
@@ -495,9 +486,9 @@
} }
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
}, },
@@ -810,9 +801,9 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@@ -896,6 +887,7 @@
"version": "7.2.3", "version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"fs.realpath": "^1.0.0", "fs.realpath": "^1.0.0",
@@ -962,14 +954,14 @@
} }
}, },
"node_modules/husky": { "node_modules/husky": {
"version": "8.0.3", "version": "9.0.11",
"resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz",
"integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==",
"bin": { "bin": {
"husky": "lib/bin.js" "husky": "bin.mjs"
}, },
"engines": { "engines": {
"node": ">=14" "node": ">=18"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/typicode" "url": "https://github.com/sponsors/typicode"
@@ -1013,6 +1005,7 @@
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "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, "dev": true,
"dependencies": { "dependencies": {
"once": "^1.3.0", "once": "^1.3.0",
@@ -1143,28 +1136,31 @@
} }
}, },
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "3.0.0", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
"integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antonk52"
} }
}, },
"node_modules/lint-staged": { "node_modules/lint-staged": {
"version": "15.2.2", "version": "15.2.5",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.5.tgz",
"integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", "integrity": "sha512-j+DfX7W9YUvdzEZl3Rk47FhDF6xwDBV5wwsCPw6BwWZVPYJemusQmvb9bRsW23Sqsaa+vRloAWogbK4BUuU2zA==",
"dependencies": { "dependencies": {
"chalk": "5.3.0", "chalk": "~5.3.0",
"commander": "11.1.0", "commander": "~12.1.0",
"debug": "4.3.4", "debug": "~4.3.4",
"execa": "8.0.1", "execa": "~8.0.1",
"lilconfig": "3.0.0", "lilconfig": "~3.1.1",
"listr2": "8.0.1", "listr2": "~8.2.1",
"micromatch": "4.0.5", "micromatch": "~4.0.7",
"pidtree": "0.6.0", "pidtree": "~0.6.0",
"string-argv": "0.3.2", "string-argv": "~0.3.2",
"yaml": "2.3.4" "yaml": "~2.4.2"
}, },
"bin": { "bin": {
"lint-staged": "bin/lint-staged.js" "lint-staged": "bin/lint-staged.js"
@@ -1188,15 +1184,15 @@
} }
}, },
"node_modules/listr2": { "node_modules/listr2": {
"version": "8.0.1", "version": "8.2.1",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz",
"integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", "integrity": "sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g==",
"dependencies": { "dependencies": {
"cli-truncate": "^4.0.0", "cli-truncate": "^4.0.0",
"colorette": "^2.0.20", "colorette": "^2.0.20",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"log-update": "^6.0.0", "log-update": "^6.0.0",
"rfdc": "^1.3.0", "rfdc": "^1.3.1",
"wrap-ansi": "^9.0.0" "wrap-ansi": "^9.0.0"
}, },
"engines": { "engines": {
@@ -1318,11 +1314,11 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.5", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"dependencies": { "dependencies": {
"braces": "^3.0.2", "braces": "^3.0.3",
"picomatch": "^2.3.1" "picomatch": "^2.3.1"
}, },
"engines": { "engines": {
@@ -1412,17 +1408,17 @@
} }
}, },
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.3", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
"integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@aashutoshrathi/word-wrap": "^1.2.3",
"deep-is": "^0.1.3", "deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6", "fast-levenshtein": "^2.0.6",
"levn": "^0.4.1", "levn": "^0.4.1",
"prelude-ls": "^1.2.1", "prelude-ls": "^1.2.1",
"type-check": "^0.4.0" "type-check": "^0.4.0",
"word-wrap": "^1.2.5"
}, },
"engines": { "engines": {
"node": ">= 0.8.0" "node": ">= 0.8.0"
@@ -1528,9 +1524,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.2.5", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.0.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "integrity": "sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==",
"dev": true, "dev": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
@@ -1666,6 +1662,7 @@
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"glob": "^7.1.3" "glob": "^7.1.3"
@@ -1967,6 +1964,15 @@
"node": ">= 8" "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": { "node_modules/wrap-ansi": {
"version": "9.0.0", "version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
@@ -2034,9 +2040,12 @@
} }
}, },
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.3.4", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
"bin": {
"yaml": "bin.mjs"
},
"engines": { "engines": {
"node": ">= 14" "node": ">= 14"
} }
+5 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "planka", "name": "planka",
"version": "1.17.2", "version": "1.19.1",
"private": true, "private": true,
"homepage": "https://plankanban.github.io/planka", "homepage": "https://plankanban.github.io/planka",
"repository": { "repository": {
@@ -18,7 +18,7 @@
"docker:build:base": "docker build -t ghcr.io/plankanban/planka:base-local -f Dockerfile.base .", "docker:build:base": "docker build -t ghcr.io/plankanban/planka:base-local -f Dockerfile.base .",
"postinstall": "(cd server && npm i && cd ../client && npm i)", "postinstall": "(cd server && npm i && cd ../client && npm i)",
"lint": "npm run server:lint && npm run client:lint", "lint": "npm run server:lint && npm run client:lint",
"prepare": "husky install", "prepare": "husky",
"server:console": "npm run console --prefix server", "server:console": "npm run console --prefix server",
"server:db:init": "npm run db:init --prefix server", "server:db:init": "npm run db:init --prefix server",
"server:db:migrate": "npm run db:migrate --prefix server", "server:db:migrate": "npm run db:migrate --prefix server",
@@ -57,13 +57,13 @@
}, },
"dependencies": { "dependencies": {
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"husky": "^8.0.3", "husky": "^9.0.1",
"lint-staged": "^15.2.2" "lint-staged": "^15.2.5"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3", "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_NAME=Demo Demo
# DEFAULT_ADMIN_USERNAME=demo # DEFAULT_ADMIN_USERNAME=demo
# ALLOW_ALL_TO_CREATE_PROJECTS=true
# OIDC_ISSUER= # OIDC_ISSUER=
# OIDC_CLIENT_ID= # OIDC_CLIENT_ID=
# OIDC_CLIENT_SECRET= # OIDC_CLIENT_SECRET=
@@ -43,6 +45,14 @@ SECRET_KEY=notsecretkey
# SMTP_PASSWORD= # SMTP_PASSWORD=
# SMTP_FROM="Demo Demo" <demo@demo.demo> # 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_BOT_TOKEN=
# SLACK_CHANNEL_ID= # SLACK_CHANNEL_ID=
+5 -2
View File
@@ -44,12 +44,12 @@ module.exports = {
async fn(inputs, exits) { async fn(inputs, exits) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { card } = await sails.helpers.cards const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId) .getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: card.boardId, boardId: board.id,
userId: currentUser.id, userId: currentUser.id,
}); });
@@ -83,6 +83,9 @@ module.exports = {
const fileData = await sails.helpers.attachments.processUploadedFile(file); const fileData = await sails.helpers.attachments.processUploadedFile(file);
const attachment = await sails.helpers.attachments.createOne.with({ const attachment = await sails.helpers.attachments.createOne.with({
project,
board,
list,
values: { values: {
...fileData, ...fileData,
card, card,
+4 -1
View File
@@ -33,7 +33,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.ATTACHMENT_NOT_FOUND); .intercept('pathNotFound', () => Errors.ATTACHMENT_NOT_FOUND);
let { attachment } = path; let { attachment } = path;
const { card, board } = path; const { card, list, board, project } = path;
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: board.id, boardId: board.id,
@@ -49,9 +49,12 @@ module.exports = {
} }
attachment = await sails.helpers.attachments.deleteOne.with({ attachment = await sails.helpers.attachments.deleteOne.with({
project,
board, board,
list,
card, card,
record: attachment, record: attachment,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+5 -1
View File
@@ -37,7 +37,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.ATTACHMENT_NOT_FOUND); .intercept('pathNotFound', () => Errors.ATTACHMENT_NOT_FOUND);
let { attachment } = path; let { attachment } = path;
const { board } = path; const { card, list, board, project } = path;
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: board.id, boardId: board.id,
@@ -56,8 +56,12 @@ module.exports = {
attachment = await sails.helpers.attachments.updateOne.with({ attachment = await sails.helpers.attachments.updateOne.with({
values, values,
project,
board, board,
list,
card,
record: attachment, record: attachment,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
@@ -48,14 +48,11 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { board } = await sails.helpers.boards const { board, project } = await sails.helpers.boards
.getProjectPath(inputs.boardId) .getProjectPath(inputs.boardId)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
const isProjectManager = await sails.helpers.users.isProjectManager( const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
currentUser.id,
board.projectId,
);
if (!isProjectManager) { if (!isProjectManager) {
throw Errors.BOARD_NOT_FOUND; // Forbidden throw Errors.BOARD_NOT_FOUND; // Forbidden
@@ -71,11 +68,13 @@ module.exports = {
const boardMembership = await sails.helpers.boardMemberships.createOne const boardMembership = await sails.helpers.boardMemberships.createOne
.with({ .with({
project,
values: { values: {
...values, ...values,
board, board,
user, user,
}, },
actorUser: currentUser,
request: this.req, request: this.req,
}) })
.intercept('userAlreadyBoardMember', () => Errors.USER_ALREADY_BOARD_MEMBER); .intercept('userAlreadyBoardMember', () => Errors.USER_ALREADY_BOARD_MEMBER);
@@ -27,7 +27,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.BOARD_MEMBERSHIP_NOT_FOUND); .intercept('pathNotFound', () => Errors.BOARD_MEMBERSHIP_NOT_FOUND);
let { boardMembership } = path; let { boardMembership } = path;
const { project } = path; const { board, project } = path;
if (boardMembership.userId !== currentUser.id) { if (boardMembership.userId !== currentUser.id) {
const isProjectManager = await sails.helpers.users.isProjectManager( const isProjectManager = await sails.helpers.users.isProjectManager(
@@ -42,7 +42,9 @@ module.exports = {
boardMembership = await sails.helpers.boardMemberships.deleteOne.with({ boardMembership = await sails.helpers.boardMemberships.deleteOne.with({
project, project,
board,
record: boardMembership, record: boardMembership,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
@@ -35,7 +35,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.BOARD_MEMBERSHIP_NOT_FOUND); .intercept('pathNotFound', () => Errors.BOARD_MEMBERSHIP_NOT_FOUND);
let { boardMembership } = path; let { boardMembership } = path;
const { project } = path; const { board, project } = path;
const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id); const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
@@ -47,7 +47,10 @@ module.exports = {
boardMembership = await sails.helpers.boardMemberships.updateOne.with({ boardMembership = await sails.helpers.boardMemberships.updateOne.with({
values, values,
project,
board,
record: boardMembership, record: boardMembership,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+1 -1
View File
@@ -103,7 +103,7 @@ module.exports = {
project, project,
}, },
import: boardImport, import: boardImport,
user: currentUser, actorUser: currentUser,
requestId: inputs.requestId, requestId: inputs.requestId,
request: this.req, request: this.req,
}); });
+7 -5
View File
@@ -22,25 +22,27 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
let { board } = await sails.helpers.boards const path = await sails.helpers.boards
.getProjectPath(inputs.id) .getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
let { board } = path;
const { project } = path;
if (!board) { if (!board) {
throw Errors.BOARD_NOT_FOUND; throw Errors.BOARD_NOT_FOUND;
} }
const isProjectManager = await sails.helpers.users.isProjectManager( const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
currentUser.id,
board.projectId,
);
if (!isProjectManager) { if (!isProjectManager) {
throw Errors.BOARD_NOT_FOUND; // Forbidden throw Errors.BOARD_NOT_FOUND; // Forbidden
} }
board = await sails.helpers.boards.deleteOne.with({ board = await sails.helpers.boards.deleteOne.with({
project,
record: board, record: board,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+7 -5
View File
@@ -29,18 +29,18 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
let { board } = await sails.helpers.boards const path = await sails.helpers.boards
.getProjectPath(inputs.id) .getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
let { board } = path;
const { project } = path;
if (!board) { if (!board) {
throw Errors.BOARD_NOT_FOUND; throw Errors.BOARD_NOT_FOUND;
} }
const isProjectManager = await sails.helpers.users.isProjectManager( const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
currentUser.id,
board.projectId,
);
if (!isProjectManager) { if (!isProjectManager) {
throw Errors.BOARD_NOT_FOUND; // Forbidden throw Errors.BOARD_NOT_FOUND; // Forbidden
@@ -50,7 +50,9 @@ module.exports = {
board = await sails.helpers.boards.updateOne.with({ board = await sails.helpers.boards.updateOne.with({
values, values,
project,
record: board, record: board,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+7 -3
View File
@@ -45,12 +45,12 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { card } = await sails.helpers.cards const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId) .getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: card.boardId, boardId: board.id,
userId: currentUser.id, userId: currentUser.id,
}); });
@@ -64,7 +64,7 @@ module.exports = {
const label = await Label.findOne({ const label = await Label.findOne({
id: inputs.labelId, id: inputs.labelId,
boardId: card.boardId, boardId: board.id,
}); });
if (!label) { if (!label) {
@@ -73,10 +73,14 @@ module.exports = {
const cardLabel = await sails.helpers.cardLabels.createOne const cardLabel = await sails.helpers.cardLabels.createOne
.with({ .with({
project,
board,
list,
values: { values: {
card, card,
label, label,
}, },
actorUser: currentUser,
request: this.req, request: this.req,
}) })
.intercept('labelAlreadyInCard', () => Errors.LABEL_ALREADY_IN_CARD); .intercept('labelAlreadyInCard', () => Errors.LABEL_ALREADY_IN_CARD);
+5 -1
View File
@@ -39,7 +39,7 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { board } = await sails.helpers.cards const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId) .getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
@@ -66,8 +66,12 @@ module.exports = {
} }
cardLabel = await sails.helpers.cardLabels.deleteOne.with({ cardLabel = await sails.helpers.cardLabels.deleteOne.with({
project,
board, board,
list,
card,
record: cardLabel, record: cardLabel,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
@@ -45,12 +45,12 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { card } = await sails.helpers.cards const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId) .getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: card.boardId, boardId: board.id,
userId: currentUser.id, userId: currentUser.id,
}); });
@@ -62,7 +62,7 @@ module.exports = {
throw Errors.NOT_ENOUGH_RIGHTS; 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) { if (!isBoardMember) {
throw Errors.USER_NOT_FOUND; throw Errors.USER_NOT_FOUND;
@@ -70,10 +70,14 @@ module.exports = {
const cardMembership = await sails.helpers.cardMemberships.createOne const cardMembership = await sails.helpers.cardMemberships.createOne
.with({ .with({
project,
board,
list,
values: { values: {
card, card,
userId: inputs.userId, userId: inputs.userId,
}, },
actorUser: currentUser,
request: this.req, request: this.req,
}) })
.intercept('userAlreadyCardMember', () => Errors.USER_ALREADY_CARD_MEMBER); .intercept('userAlreadyCardMember', () => Errors.USER_ALREADY_CARD_MEMBER);
@@ -39,7 +39,7 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { board } = await sails.helpers.cards const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId) .getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
@@ -66,8 +66,12 @@ module.exports = {
} }
cardMembership = await sails.helpers.cardMemberships.deleteOne.with({ cardMembership = await sails.helpers.cardMemberships.deleteOne.with({
project,
board, board,
list,
card,
record: cardMembership, record: cardMembership,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+2 -1
View File
@@ -78,7 +78,7 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { board, list } = await sails.helpers.lists const { list, board, project } = await sails.helpers.lists
.getProjectPath(inputs.listId) .getProjectPath(inputs.listId)
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND); .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
@@ -99,6 +99,7 @@ module.exports = {
const card = await sails.helpers.cards.createOne const card = await sails.helpers.cards.createOne
.with({ .with({
project,
board, board,
values: { values: {
...values, ...values,
+9 -3
View File
@@ -28,12 +28,15 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
let { card } = await sails.helpers.cards const path = await sails.helpers.cards
.getProjectPath(inputs.id) .getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
let { card } = path;
const { list, board, project } = path;
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: card.boardId, boardId: board.id,
userId: currentUser.id, userId: currentUser.id,
}); });
@@ -46,8 +49,11 @@ module.exports = {
} }
card = await sails.helpers.cards.deleteOne.with({ card = await sails.helpers.cards.deleteOne.with({
project,
board,
list,
record: card, record: card,
user: currentUser, actorUser: currentUser,
request: this.req, request: this.req,
}); });
+3 -2
View File
@@ -35,12 +35,12 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { card, list, board } = await sails.helpers.cards const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.id) .getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: card.boardId, boardId: board.id,
userId: currentUser.id, userId: currentUser.id,
}); });
@@ -60,6 +60,7 @@ module.exports = {
cardLabels, cardLabels,
tasks, tasks,
} = await sails.helpers.cards.duplicateOne.with({ } = await sails.helpers.cards.duplicateOne.with({
project,
board, board,
list, list,
record: card, record: card,
+7 -3
View File
@@ -118,7 +118,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
let { card } = path; let { card } = path;
const { list, board } = path; const { list, board, project } = path;
let boardMembership = await BoardMembership.findOne({ let boardMembership = await BoardMembership.findOne({
boardId: board.id, boardId: board.id,
@@ -133,9 +133,11 @@ module.exports = {
throw Errors.NOT_ENOUGH_RIGHTS; throw Errors.NOT_ENOUGH_RIGHTS;
} }
let nextProject;
let nextBoard; let nextBoard;
if (!_.isUndefined(inputs.boardId)) { if (!_.isUndefined(inputs.boardId)) {
({ board: nextBoard } = await sails.helpers.boards ({ board: nextBoard, project: nextProject } = await sails.helpers.boards
.getProjectPath(inputs.boardId) .getProjectPath(inputs.boardId)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND)); .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND));
@@ -177,15 +179,17 @@ module.exports = {
card = await sails.helpers.cards.updateOne card = await sails.helpers.cards.updateOne
.with({ .with({
project,
board, board,
list, list,
record: card, record: card,
values: { values: {
...values, ...values,
project: nextProject,
board: nextBoard, board: nextBoard,
list: nextList, list: nextList,
}, },
user: currentUser, actorUser: currentUser,
request: this.req, request: this.req,
}) })
.intercept('positionMustBeInValues', () => Errors.POSITION_MUST_BE_PRESENT) .intercept('positionMustBeInValues', () => Errors.POSITION_MUST_BE_PRESENT)
@@ -32,7 +32,7 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { board, card } = await sails.helpers.cards const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId) .getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
@@ -55,7 +55,9 @@ module.exports = {
}; };
const action = await sails.helpers.actions.createOne.with({ const action = await sails.helpers.actions.createOne.with({
project,
board, board,
list,
values: { values: {
...values, ...values,
card, card,
@@ -36,7 +36,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.COMMENT_ACTION_NOT_FOUND); .intercept('pathNotFound', () => Errors.COMMENT_ACTION_NOT_FOUND);
let { action } = path; let { action } = path;
const { board, project } = path; const { card, list, board, project } = path;
const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id); const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
@@ -60,8 +60,12 @@ module.exports = {
} }
action = await sails.helpers.actions.deleteOne.with({ action = await sails.helpers.actions.deleteOne.with({
project,
board, board,
list,
card,
record: action, record: action,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
@@ -40,7 +40,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.COMMENT_ACTION_NOT_FOUND); .intercept('pathNotFound', () => Errors.COMMENT_ACTION_NOT_FOUND);
let { action } = path; let { action } = path;
const { board, project } = path; const { card, list, board, project } = path;
const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id); const isProjectManager = await sails.helpers.users.isProjectManager(currentUser.id, project.id);
@@ -69,8 +69,12 @@ module.exports = {
action = await sails.helpers.actions.updateOne.with({ action = await sails.helpers.actions.updateOne.with({
values, values,
project,
board, board,
list,
card,
record: action, record: action,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+3 -1
View File
@@ -42,7 +42,7 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { board } = await sails.helpers.boards const { board, project } = await sails.helpers.boards
.getProjectPath(inputs.boardId) .getProjectPath(inputs.boardId)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
@@ -62,10 +62,12 @@ module.exports = {
const values = _.pick(inputs, ['position', 'name', 'color']); const values = _.pick(inputs, ['position', 'name', 'color']);
const label = await sails.helpers.labels.createOne.with({ const label = await sails.helpers.labels.createOne.with({
project,
values: { values: {
...values, ...values,
board, board,
}, },
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+8 -2
View File
@@ -28,12 +28,15 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
let { label } = await sails.helpers.labels const path = await sails.helpers.labels
.getProjectPath(inputs.id) .getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.LABEL_NOT_FOUND); .intercept('pathNotFound', () => Errors.LABEL_NOT_FOUND);
let { label } = path;
const { board, project } = path;
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: label.boardId, boardId: board.id,
userId: currentUser.id, userId: currentUser.id,
}); });
@@ -46,7 +49,10 @@ module.exports = {
} }
label = await sails.helpers.labels.deleteOne.with({ label = await sails.helpers.labels.deleteOne.with({
project,
board,
record: label, record: label,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+8 -2
View File
@@ -40,12 +40,15 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
let { label } = await sails.helpers.labels const path = await sails.helpers.labels
.getProjectPath(inputs.id) .getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.LABEL_NOT_FOUND); .intercept('pathNotFound', () => Errors.LABEL_NOT_FOUND);
let { label } = path;
const { board, project } = path;
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: label.boardId, boardId: board.id,
userId: currentUser.id, userId: currentUser.id,
}); });
@@ -61,7 +64,10 @@ module.exports = {
label = await sails.helpers.labels.updateOne.with({ label = await sails.helpers.labels.updateOne.with({
values, values,
project,
board,
record: label, record: label,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+3 -1
View File
@@ -36,7 +36,7 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { board } = await sails.helpers.boards const { board, project } = await sails.helpers.boards
.getProjectPath(inputs.boardId) .getProjectPath(inputs.boardId)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
@@ -56,10 +56,12 @@ module.exports = {
const values = _.pick(inputs, ['position', 'name']); const values = _.pick(inputs, ['position', 'name']);
const list = await sails.helpers.lists.createOne.with({ const list = await sails.helpers.lists.createOne.with({
project,
values: { values: {
...values, ...values,
board, board,
}, },
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+8 -2
View File
@@ -28,12 +28,15 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
let { list } = await sails.helpers.lists const path = await sails.helpers.lists
.getProjectPath(inputs.id) .getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND); .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
let { list } = path;
const { board, project } = path;
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: list.boardId, boardId: board.id,
userId: currentUser.id, userId: currentUser.id,
}); });
@@ -46,7 +49,10 @@ module.exports = {
} }
list = await sails.helpers.lists.deleteOne.with({ list = await sails.helpers.lists.deleteOne.with({
project,
board,
record: list, record: list,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+5 -2
View File
@@ -32,12 +32,12 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { list } = await sails.helpers.lists const { list, board, project } = await sails.helpers.lists
.getProjectPath(inputs.id) .getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND); .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: list.boardId, boardId: board.id,
userId: currentUser.id, userId: currentUser.id,
}); });
@@ -50,8 +50,11 @@ module.exports = {
} }
const cards = await sails.helpers.lists.sortOne.with({ const cards = await sails.helpers.lists.sortOne.with({
project,
board,
record: list, record: list,
type: inputs.type, type: inputs.type,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+8 -2
View File
@@ -35,12 +35,15 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
let { list } = await sails.helpers.lists const path = await sails.helpers.lists
.getProjectPath(inputs.id) .getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND); .intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
let { list } = path;
const { board, project } = path;
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: list.boardId, boardId: board.id,
userId: currentUser.id, userId: currentUser.id,
}); });
@@ -56,7 +59,10 @@ module.exports = {
list = await sails.helpers.lists.updateOne.with({ list = await sails.helpers.lists.updateOne.with({
values, values,
project,
board,
record: list, record: list,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
@@ -18,7 +18,7 @@ module.exports = {
const notifications = await sails.helpers.notifications.updateMany.with({ const notifications = await sails.helpers.notifications.updateMany.with({
values, values,
recordsOrIds: inputs.ids.split(','), recordsOrIds: inputs.ids.split(','),
user: currentUser, actorUser: currentUser,
request: this.req, request: this.req,
}); });
@@ -63,6 +63,7 @@ module.exports = {
project, project,
user, user,
}, },
actorUser: currentUser,
request: this.req, request: this.req,
}) })
.intercept('userAlreadyProjectManager', () => Errors.USER_ALREADY_PROJECT_MANAGER); .intercept('userAlreadyProjectManager', () => Errors.USER_ALREADY_PROJECT_MANAGER);
@@ -40,6 +40,7 @@ module.exports = {
// TODO: check if the last one // TODO: check if the last one
projectManager = await sails.helpers.projectManagers.deleteOne.with({ projectManager = await sails.helpers.projectManagers.deleteOne.with({
record: projectManager, record: projectManager,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+17 -1
View File
@@ -1,3 +1,9 @@
const Errors = {
NOT_ENOUGH_RIGHTS: {
notEnoughRights: 'Not enough rights',
},
};
module.exports = { module.exports = {
inputs: { inputs: {
name: { name: {
@@ -6,14 +12,24 @@ module.exports = {
}, },
}, },
exits: {
notEnoughRights: {
responseType: 'forbidden',
},
},
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
if (!currentUser.isAdmin && !sails.config.custom.allowAllToCreateProjects) {
throw Errors.NOT_ENOUGH_RIGHTS;
}
const values = _.pick(inputs, ['name']); const values = _.pick(inputs, ['name']);
const { project, projectManager } = await sails.helpers.projects.createOne.with({ const { project, projectManager } = await sails.helpers.projects.createOne.with({
values, values,
user: currentUser, actorUser: currentUser,
request: this.req, request: this.req,
}); });
@@ -36,6 +36,7 @@ module.exports = {
project = await sails.helpers.projects.deleteOne.with({ project = await sails.helpers.projects.deleteOne.with({
record: project, record: project,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
@@ -90,6 +90,7 @@ module.exports = {
values: { values: {
backgroundImage: fileData, backgroundImage: fileData,
}, },
actorUser: currentUser,
request: this.req, request: this.req,
}); });
@@ -81,6 +81,7 @@ module.exports = {
project = await sails.helpers.projects.updateOne.with({ project = await sails.helpers.projects.updateOne.with({
values, values,
record: project, record: project,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+1
View File
@@ -17,6 +17,7 @@ module.exports = {
return { return {
item: { item: {
oidc, oidc,
allowAllToCreateProjects: sails.config.custom.allowAllToCreateProjects,
}, },
}; };
}, },
+6 -2
View File
@@ -39,12 +39,12 @@ module.exports = {
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; const { currentUser } = this.req;
const { card } = await sails.helpers.cards const { card, list, board, project } = await sails.helpers.cards
.getProjectPath(inputs.cardId) .getProjectPath(inputs.cardId)
.intercept('pathNotFound', () => Errors.CARD_NOT_FOUND); .intercept('pathNotFound', () => Errors.CARD_NOT_FOUND);
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: card.boardId, boardId: board.id,
userId: currentUser.id, userId: currentUser.id,
}); });
@@ -59,10 +59,14 @@ module.exports = {
const values = _.pick(inputs, ['position', 'name', 'isCompleted']); const values = _.pick(inputs, ['position', 'name', 'isCompleted']);
const task = await sails.helpers.tasks.createOne.with({ const task = await sails.helpers.tasks.createOne.with({
project,
board,
list,
values: { values: {
...values, ...values,
card, card,
}, },
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+5 -1
View File
@@ -33,7 +33,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.TASK_NOT_FOUND); .intercept('pathNotFound', () => Errors.TASK_NOT_FOUND);
let { task } = path; let { task } = path;
const { board } = path; const { card, list, board, project } = path;
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: board.id, boardId: board.id,
@@ -49,8 +49,12 @@ module.exports = {
} }
task = await sails.helpers.tasks.deleteOne.with({ task = await sails.helpers.tasks.deleteOne.with({
project,
board, board,
list,
card,
record: task, record: task,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+5 -1
View File
@@ -43,7 +43,7 @@ module.exports = {
.intercept('pathNotFound', () => Errors.TASK_NOT_FOUND); .intercept('pathNotFound', () => Errors.TASK_NOT_FOUND);
let { task } = path; let { task } = path;
const { board } = path; const { card, list, board, project } = path;
const boardMembership = await BoardMembership.findOne({ const boardMembership = await BoardMembership.findOne({
boardId: board.id, boardId: board.id,
@@ -62,8 +62,12 @@ module.exports = {
task = await sails.helpers.tasks.updateOne.with({ task = await sails.helpers.tasks.updateOne.with({
values, values,
project,
board, board,
list,
card,
record: task, record: task,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
+3
View File
@@ -71,6 +71,8 @@ module.exports = {
}, },
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req;
if (sails.config.custom.oidcEnforced) { if (sails.config.custom.oidcEnforced) {
throw Errors.NOT_ENOUGH_RIGHTS; throw Errors.NOT_ENOUGH_RIGHTS;
} }
@@ -89,6 +91,7 @@ module.exports = {
const user = await sails.helpers.users.createOne const user = await sails.helpers.users.createOne
.with({ .with({
values, values,
actorUser: currentUser,
request: this.req, request: this.req,
}) })
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE) .intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE)
+3
View File
@@ -26,6 +26,8 @@ module.exports = {
}, },
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req;
let user = await sails.helpers.users.getOne(inputs.id); let user = await sails.helpers.users.getOne(inputs.id);
if (!user) { if (!user) {
@@ -38,6 +40,7 @@ module.exports = {
user = await sails.helpers.users.deleteOne.with({ user = await sails.helpers.users.deleteOne.with({
record: user, record: user,
actorUser: currentUser,
request: this.req, request: this.req,
}); });
@@ -91,7 +91,7 @@ module.exports = {
values: { values: {
avatar: fileData, avatar: fileData,
}, },
user: currentUser, actorUser: currentUser,
request: this.req, request: this.req,
}); });
+1 -1
View File
@@ -82,7 +82,7 @@ module.exports = {
.with({ .with({
values, values,
record: user, record: user,
user: currentUser, actorUser: currentUser,
request: this.req, request: this.req,
}) })
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE); .intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE);
@@ -80,7 +80,7 @@ module.exports = {
user = await sails.helpers.users.updateOne.with({ user = await sails.helpers.users.updateOne.with({
values, values,
record: user, record: user,
user: currentUser, actorUser: currentUser,
request: this.req, request: this.req,
}); });
@@ -83,7 +83,7 @@ module.exports = {
.with({ .with({
values, values,
record: user, record: user,
user: currentUser, actorUser: currentUser,
request: this.req, request: this.req,
}) })
.intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE); .intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE);
+1 -1
View File
@@ -95,7 +95,7 @@ module.exports = {
user = await sails.helpers.users.updateOne.with({ user = await sails.helpers.users.updateOne.with({
values, values,
record: user, record: user,
user: currentUser, actorUser: currentUser,
request: this.req, request: this.req,
}); });
+33 -9
View File
@@ -14,21 +14,21 @@ const valuesValidator = (value) => {
return true; 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}>`; const cardLink = `<${sails.config.custom.baseUrl}/cards/${card.id}|${card.name}>`;
let markdown; let markdown;
switch (action.type) { switch (action.type) {
case Action.Types.CREATE_CARD: 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; break;
case Action.Types.MOVE_CARD: 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; break;
case Action.Types.COMMENT_CARD: 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; break;
default: default:
@@ -45,10 +45,18 @@ module.exports = {
custom: valuesValidator, custom: valuesValidator,
required: true, required: true,
}, },
project: {
type: 'ref',
required: true,
},
board: { board: {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
list: {
type: 'ref',
required: true,
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@@ -72,6 +80,24 @@ module.exports = {
inputs.request, 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( const subscriptionUserIds = await sails.helpers.cards.getSubscriptionUserIds(
action.cardId, action.cardId,
action.userId, action.userId,
@@ -84,17 +110,15 @@ module.exports = {
userId, userId,
action, action,
}, },
user: values.user, project: inputs.project,
board: inputs.board, board: inputs.board,
list: inputs.list,
card: values.card, card: values.card,
actorUser: values.user,
}), }),
), ),
); );
if (sails.config.custom.slackBotToken) {
buildAndSendSlackMessage(values.user, values.card, action);
}
return action; return action;
}, },
}; };
+30
View File
@@ -4,10 +4,26 @@ module.exports = {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
project: {
type: 'ref',
required: true,
},
board: { board: {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
list: {
type: 'ref',
required: true,
},
card: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@@ -25,6 +41,20 @@ module.exports = {
}, },
inputs.request, 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; return action;
+30
View File
@@ -8,10 +8,26 @@ module.exports = {
type: 'json', type: 'json',
required: true, required: true,
}, },
project: {
type: 'ref',
required: true,
},
board: { board: {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
list: {
type: 'ref',
required: true,
},
card: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@@ -31,6 +47,20 @@ module.exports = {
}, },
inputs.request, 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; return action;
@@ -21,6 +21,18 @@ module.exports = {
custom: valuesValidator, custom: valuesValidator,
required: true, required: true,
}, },
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
list: {
type: 'ref',
required: true,
},
requestId: { requestId: {
type: 'string', type: 'string',
isNotEmptyString: true, isNotEmptyString: true,
@@ -49,12 +61,30 @@ module.exports = {
inputs.request, 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) { if (!values.card.coverAttachmentId && attachment.image) {
await sails.helpers.cards.updateOne.with({ await sails.helpers.cards.updateOne.with({
record: values.card, record: values.card,
values: { values: {
coverAttachmentId: attachment.id, coverAttachmentId: attachment.id,
}, },
project: inputs.project,
board: inputs.board,
list: inputs.list,
actorUser: values.creatorUser,
}); });
} }
@@ -7,14 +7,26 @@ module.exports = {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
project: {
type: 'ref',
required: true,
},
board: { board: {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
list: {
type: 'ref',
required: true,
},
card: { card: {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
actorUser: {
type: 'ref',
required: true,
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@@ -27,6 +39,10 @@ module.exports = {
values: { values: {
coverAttachmentId: null, coverAttachmentId: null,
}, },
project: inputs.project,
board: inputs.board,
list: inputs.list,
actorUser: inputs.actorUser,
request: inputs.request, request: inputs.request,
}); });
} }
@@ -48,6 +64,20 @@ module.exports = {
}, },
inputs.request, 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; return attachment;
@@ -8,10 +8,26 @@ module.exports = {
type: 'json', type: 'json',
required: true, required: true,
}, },
project: {
type: 'ref',
required: true,
},
board: { board: {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
list: {
type: 'ref',
required: true,
},
card: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@@ -31,6 +47,20 @@ module.exports = {
}, },
inputs.request, 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; return attachment;
@@ -21,6 +21,14 @@ module.exports = {
custom: valuesValidator, custom: valuesValidator,
required: true, required: true,
}, },
project: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@@ -67,6 +75,19 @@ module.exports = {
inputs.request, 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; return boardMembership;
}, },
}; };
@@ -10,6 +10,14 @@ module.exports = {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
board: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: { request: {
type: 'ref', 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; return boardMembership;
@@ -8,6 +8,18 @@ module.exports = {
type: 'json', type: 'json',
required: true, required: true,
}, },
project: {
type: 'ref',
required: true,
},
board: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@@ -40,6 +52,18 @@ module.exports = {
}, },
inputs.request, inputs.request,
); );
sails.helpers.utils.sendWebhooks.with({
event: 'boardMembershipUpdate',
data: {
item: boardMembership,
included: {
projects: [inputs.project],
boards: [inputs.board],
},
},
user: inputs.actorUser,
});
} }
return boardMembership; return boardMembership;
+16 -3
View File
@@ -37,7 +37,7 @@ module.exports = {
type: 'json', type: 'json',
custom: importValidator, custom: importValidator,
}, },
user: { actorUser: {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
@@ -80,6 +80,8 @@ module.exports = {
position: nextPosition, position: nextPosition,
}, },
}); });
// TODO: send webhooks
}); });
}); });
@@ -90,12 +92,12 @@ module.exports = {
}).fetch(); }).fetch();
if (inputs.import && inputs.import.type === Board.ImportTypes.TRELLO) { 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({ const boardMembership = await BoardMembership.create({
boardId: board.id, boardId: board.id,
userId: inputs.user.id, userId: inputs.actorUser.id,
role: BoardMembership.Roles.EDITOR, role: BoardMembership.Roles.EDITOR,
}).fetch(); }).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 { return {
board, board,
boardMembership, boardMembership,
+19
View File
@@ -4,6 +4,14 @@ module.exports = {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
project: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@@ -33,6 +41,17 @@ module.exports = {
inputs.request, inputs.request,
); );
}); });
sails.helpers.utils.sendWebhooks.with({
event: 'boardDelete',
data: {
item: board,
included: {
projects: [inputs.project],
},
},
user: inputs.actorUser,
});
} }
return board; return board;
@@ -2,10 +2,6 @@ const POSITION_GAP = 65535; // TODO: move to config
module.exports = { module.exports = {
inputs: { inputs: {
user: {
type: 'ref',
required: true,
},
board: { board: {
type: 'ref', type: 'ref',
required: true, required: true,
@@ -14,6 +10,10 @@ module.exports = {
type: 'json', type: 'json',
required: true, required: true,
}, },
actorUser: {
type: 'ref',
required: true,
},
}, },
async fn(inputs) { async fn(inputs) {
@@ -87,7 +87,7 @@ module.exports = {
trelloComments.map(async (trelloComment) => { trelloComments.map(async (trelloComment) => {
return Action.create({ return Action.create({
cardId: plankaCard.id, cardId: plankaCard.id,
userId: inputs.user.id, userId: inputs.actorUser.id,
type: 'commentCard', type: 'commentCard',
data: { data: {
text: text:
@@ -105,7 +105,7 @@ module.exports = {
const plankaCard = await Card.create({ const plankaCard = await Card.create({
boardId: inputs.board.id, boardId: inputs.board.id,
listId: plankaList.id, listId: plankaList.id,
creatorUserId: inputs.user.id, creatorUserId: inputs.actorUser.id,
position: trelloCard.pos, position: trelloCard.pos,
name: trelloCard.name, name: trelloCard.name,
description: trelloCard.desc || null, description: trelloCard.desc || null,
+21
View File
@@ -21,6 +21,14 @@ module.exports = {
custom: valuesValidator, custom: valuesValidator,
required: true, required: true,
}, },
project: {
type: 'ref',
required: true,
},
actorUser: {
type: 'ref',
required: true,
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@@ -64,6 +72,8 @@ module.exports = {
position: nextPosition, position: nextPosition,
}, },
}); });
// TODO: send webhooks
}); });
}); });
} }
@@ -81,6 +91,17 @@ module.exports = {
inputs.request, inputs.request,
); );
}); });
sails.helpers.utils.sendWebhooks.with({
event: 'boardUpdate',
data: {
item: board,
included: {
projects: [inputs.project],
},
},
user: inputs.actorUser,
});
} }
return board; return board;

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