mirror of
https://github.com/plankanban/planka.git
synced 2025-12-18 01:11:13 +03:00
Compare commits
5 Commits
v1.22.0
...
planka-0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2081b44874 | ||
|
|
0b729bf4b3 | ||
|
|
368ead982e | ||
|
|
1217969e22 | ||
|
|
e410e21363 |
@@ -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.2.8
|
version: 0.2.9
|
||||||
|
|
||||||
# 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.22.0"
|
appVersion: "1.23.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
- alias: postgresql
|
- alias: postgresql
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ metadata:
|
|||||||
name: {{ include "planka.fullname" . }}
|
name: {{ include "planka.fullname" . }}
|
||||||
labels:
|
labels:
|
||||||
{{- include "planka.labels" . | nindent 4 }}
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.service.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
spec:
|
spec:
|
||||||
type: {{ .Values.service.type }}
|
type: {{ .Values.service.type }}
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ securityContext: {}
|
|||||||
# runAsUser: 1000
|
# runAsUser: 1000
|
||||||
|
|
||||||
service:
|
service:
|
||||||
|
annotations: {}
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
port: 1337
|
port: 1337
|
||||||
## @param service.containerPort Planka HTTP container port
|
## @param service.containerPort Planka HTTP container port
|
||||||
|
|||||||
252
client/src/locales/ar-YE/core.js
Normal file
252
client/src/locales/ar-YE/core.js
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
import dateFns from 'date-fns/locale/ar';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
dateFns,
|
||||||
|
|
||||||
|
format: {
|
||||||
|
date: 'M/d/yyyy',
|
||||||
|
time: 'p',
|
||||||
|
dateTime: '$t(format:date) $t(format:time)',
|
||||||
|
longDate: 'MMM d',
|
||||||
|
longDateTime: "MMMM d 'at' p",
|
||||||
|
fullDate: 'MMM d, y',
|
||||||
|
fullDateTime: "MMMM d, y 'at' 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: 'الكشف تلقائياً',
|
||||||
|
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: 'غادر المشروع',
|
||||||
|
linkIsCopied: 'تم نسخ الرابط',
|
||||||
|
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: 'إضافة مستخدم',
|
||||||
|
copyLink_title: 'نسخ الرابط',
|
||||||
|
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}} hidden)',
|
||||||
|
showDetails: 'إظهار التفاصيل',
|
||||||
|
showFewerAttachments: 'عرض مرفقات أقل',
|
||||||
|
sortList_title: 'فرز القائمة',
|
||||||
|
start: 'ابدأ',
|
||||||
|
stop: 'توقف',
|
||||||
|
subscribe: 'اشترك',
|
||||||
|
unsubscribe: 'إلغاء الاشتراك',
|
||||||
|
uploadNewAvatar: 'رفع صورة رمزية جديدة',
|
||||||
|
uploadNewImage: 'رفع صورة جديدة',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
8
client/src/locales/ar-YE/index.js
Normal file
8
client/src/locales/ar-YE/index.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'ar-YE',
|
||||||
|
country: 'ye',
|
||||||
|
name: 'العربية',
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
||||||
23
client/src/locales/ar-YE/login.js
Normal file
23
client/src/locales/ar-YE/login.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
export default {
|
||||||
|
translation: {
|
||||||
|
common: {
|
||||||
|
emailOrUsername: 'البريد الإلكتروني أو اسم المستخدم',
|
||||||
|
invalidEmailOrUsername: 'البريد الإلكتروني أو اسم المستخدم غير صالح',
|
||||||
|
invalidCredentials: 'بيانات الاعتماد غير صالحة',
|
||||||
|
invalidPassword: 'كلمة المرور غير صالحة',
|
||||||
|
logInToPlanka: 'تسجيل الدخول إلى Planka',
|
||||||
|
noInternetConnection: 'لا يوجد اتصال بالإنترنت',
|
||||||
|
pageNotFound_title: 'الصفحة غير موجودة',
|
||||||
|
password: 'كلمة المرور',
|
||||||
|
projectManagement: 'إدارة المشروع',
|
||||||
|
serverConnectionFailed: 'فشل الاتصال بالخادم',
|
||||||
|
unknownError: 'خطأ غير معروف، يرجى المحاولة لاحقاً',
|
||||||
|
useSingleSignOn: 'استخدم تسجيل الدخول الموحد',
|
||||||
|
},
|
||||||
|
|
||||||
|
action: {
|
||||||
|
logIn: 'تسجيل الدخول',
|
||||||
|
logInWithSSO: 'تسجيل الدخول باستخدام SSO',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import arYE from './ar-YE';
|
||||||
import bgBG from './bg-BG';
|
import bgBG from './bg-BG';
|
||||||
import csCZ from './cs-CZ';
|
import csCZ from './cs-CZ';
|
||||||
import daDK from './da-DK';
|
import daDK from './da-DK';
|
||||||
@@ -25,6 +26,7 @@ import zhCN from './zh-CN';
|
|||||||
import zhTW from './zh-TW';
|
import zhTW from './zh-TW';
|
||||||
|
|
||||||
const locales = [
|
const locales = [
|
||||||
|
arYE,
|
||||||
bgBG,
|
bgBG,
|
||||||
csCZ,
|
csCZ,
|
||||||
daDK,
|
daDK,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export default '1.22.0';
|
export default '1.23.0';
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ services:
|
|||||||
# - OIDC_RESPONSE_MODE=fragment
|
# - OIDC_RESPONSE_MODE=fragment
|
||||||
# - OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
# - OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
||||||
# - OIDC_ADMIN_ROLES=admin
|
# - OIDC_ADMIN_ROLES=admin
|
||||||
|
# - OIDC_CLAIMS_SOURCE=userinfo
|
||||||
# - OIDC_EMAIL_ATTRIBUTE=email
|
# - OIDC_EMAIL_ATTRIBUTE=email
|
||||||
# - OIDC_NAME_ATTRIBUTE=name
|
# - OIDC_NAME_ATTRIBUTE=name
|
||||||
# - OIDC_USERNAME_ATTRIBUTE=preferred_username
|
# - OIDC_USERNAME_ATTRIBUTE=preferred_username
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ services:
|
|||||||
# - OIDC_RESPONSE_MODE=fragment
|
# - OIDC_RESPONSE_MODE=fragment
|
||||||
# - OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
# - OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
||||||
# - OIDC_ADMIN_ROLES=admin
|
# - OIDC_ADMIN_ROLES=admin
|
||||||
|
# - OIDC_CLAIMS_SOURCE=userinfo
|
||||||
# - OIDC_EMAIL_ATTRIBUTE=email
|
# - OIDC_EMAIL_ATTRIBUTE=email
|
||||||
# - OIDC_NAME_ATTRIBUTE=name
|
# - OIDC_NAME_ATTRIBUTE=name
|
||||||
# - OIDC_USERNAME_ATTRIBUTE=preferred_username
|
# - OIDC_USERNAME_ATTRIBUTE=preferred_username
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "planka",
|
"name": "planka",
|
||||||
"version": "1.22.0",
|
"version": "1.23.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "planka",
|
"name": "planka",
|
||||||
"version": "1.22.0",
|
"version": "1.23.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "planka",
|
"name": "planka",
|
||||||
"version": "1.22.0",
|
"version": "1.23.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://plankanban.github.io/planka",
|
"homepage": "https://plankanban.github.io/planka",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ SECRET_KEY=notsecretkey
|
|||||||
|
|
||||||
## Optional
|
## Optional
|
||||||
|
|
||||||
|
# LOG_FILE=
|
||||||
|
|
||||||
# TRUST_PROXY=0
|
# TRUST_PROXY=0
|
||||||
# TOKEN_EXPIRES_IN=365 # In days
|
# TOKEN_EXPIRES_IN=365 # In days
|
||||||
|
|
||||||
@@ -35,6 +37,7 @@ SECRET_KEY=notsecretkey
|
|||||||
# OIDC_RESPONSE_MODE=fragment
|
# OIDC_RESPONSE_MODE=fragment
|
||||||
# OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
# OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
||||||
# OIDC_ADMIN_ROLES=admin
|
# OIDC_ADMIN_ROLES=admin
|
||||||
|
# OIDC_CLAIMS_SOURCE=userinfo
|
||||||
# OIDC_EMAIL_ATTRIBUTE=email
|
# OIDC_EMAIL_ATTRIBUTE=email
|
||||||
# OIDC_NAME_ATTRIBUTE=name
|
# OIDC_NAME_ATTRIBUTE=name
|
||||||
# OIDC_USERNAME_ATTRIBUTE=preferred_username
|
# OIDC_USERNAME_ATTRIBUTE=preferred_username
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ const Errors = {
|
|||||||
INVALID_CODE_OR_NONCE: {
|
INVALID_CODE_OR_NONCE: {
|
||||||
invalidCodeOrNonce: 'Invalid code or nonce',
|
invalidCodeOrNonce: 'Invalid code or nonce',
|
||||||
},
|
},
|
||||||
INVALID_USERINFO_SIGNATURE: {
|
INVALID_USERINFO_CONFIGURATION: {
|
||||||
invalidUserinfoSignature: 'Invalid signature on userinfo due to client misconfiguration',
|
invalidUserinfoConfiguration: 'Invalid userinfo configuration',
|
||||||
},
|
},
|
||||||
EMAIL_ALREADY_IN_USE: {
|
EMAIL_ALREADY_IN_USE: {
|
||||||
emailAlreadyInUse: 'Email already in use',
|
emailAlreadyInUse: 'Email already in use',
|
||||||
@@ -40,7 +40,7 @@ module.exports = {
|
|||||||
invalidCodeOrNonce: {
|
invalidCodeOrNonce: {
|
||||||
responseType: 'unauthorized',
|
responseType: 'unauthorized',
|
||||||
},
|
},
|
||||||
invalidUserinfoSignature: {
|
invalidUserinfoConfiguration: {
|
||||||
responseType: 'unauthorized',
|
responseType: 'unauthorized',
|
||||||
},
|
},
|
||||||
emailAlreadyInUse: {
|
emailAlreadyInUse: {
|
||||||
@@ -63,7 +63,7 @@ module.exports = {
|
|||||||
sails.log.warn(`Invalid code or nonce! (IP: ${remoteAddress})`);
|
sails.log.warn(`Invalid code or nonce! (IP: ${remoteAddress})`);
|
||||||
return Errors.INVALID_CODE_OR_NONCE;
|
return Errors.INVALID_CODE_OR_NONCE;
|
||||||
})
|
})
|
||||||
.intercept('invalidUserinfoSignature', () => Errors.INVALID_USERINFO_SIGNATURE)
|
.intercept('invalidUserinfoConfiguration', () => Errors.INVALID_USERINFO_CONFIGURATION)
|
||||||
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE)
|
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE)
|
||||||
.intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE)
|
.intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE)
|
||||||
.intercept('missingValues', () => Errors.MISSING_VALUES);
|
.intercept('missingValues', () => Errors.MISSING_VALUES);
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
const util = require('util');
|
|
||||||
const { v4: uuid } = require('uuid');
|
|
||||||
|
|
||||||
const Errors = {
|
const Errors = {
|
||||||
NOT_ENOUGH_RIGHTS: {
|
NOT_ENOUGH_RIGHTS: {
|
||||||
notEnoughRights: 'Not enough rights',
|
notEnoughRights: 'Not enough rights',
|
||||||
@@ -61,16 +58,9 @@ module.exports = {
|
|||||||
throw Errors.NOT_ENOUGH_RIGHTS;
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const upload = util.promisify((options, callback) =>
|
|
||||||
this.req.file('file').upload(options, (error, files) => callback(error, files)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let files;
|
let files;
|
||||||
try {
|
try {
|
||||||
files = await upload({
|
files = await sails.helpers.utils.receiveFile('file', this.req);
|
||||||
saveAs: uuid(),
|
|
||||||
maxBytes: null,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return exits.uploadError(error.message); // TODO: add error
|
return exits.uploadError(error.message); // TODO: add error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
const util = require('util');
|
|
||||||
const { v4: uuid } = require('uuid');
|
|
||||||
|
|
||||||
const Errors = {
|
const Errors = {
|
||||||
PROJECT_NOT_FOUND: {
|
PROJECT_NOT_FOUND: {
|
||||||
projectNotFound: 'Project not found',
|
projectNotFound: 'Project not found',
|
||||||
@@ -69,16 +66,9 @@ module.exports = {
|
|||||||
|
|
||||||
let boardImport;
|
let boardImport;
|
||||||
if (inputs.importType && Object.values(Board.ImportTypes).includes(inputs.importType)) {
|
if (inputs.importType && Object.values(Board.ImportTypes).includes(inputs.importType)) {
|
||||||
const upload = util.promisify((options, callback) =>
|
|
||||||
this.req.file('importFile').upload(options, (error, files) => callback(error, files)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let files;
|
let files;
|
||||||
try {
|
try {
|
||||||
files = await upload({
|
files = await sails.helpers.utils.receiveFile('importFile', this.req);
|
||||||
saveAs: uuid(),
|
|
||||||
maxBytes: null,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return exits.uploadError(error.message); // TODO: add error
|
return exits.uploadError(error.message); // TODO: add error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
const util = require('util');
|
|
||||||
const rimraf = require('rimraf');
|
const rimraf = require('rimraf');
|
||||||
const { v4: uuid } = require('uuid');
|
|
||||||
|
|
||||||
const Errors = {
|
const Errors = {
|
||||||
PROJECT_NOT_FOUND: {
|
PROJECT_NOT_FOUND: {
|
||||||
@@ -53,16 +51,9 @@ module.exports = {
|
|||||||
throw Errors.PROJECT_NOT_FOUND; // Forbidden
|
throw Errors.PROJECT_NOT_FOUND; // Forbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
const upload = util.promisify((options, callback) =>
|
|
||||||
this.req.file('file').upload(options, (error, files) => callback(error, files)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let files;
|
let files;
|
||||||
try {
|
try {
|
||||||
files = await upload({
|
files = await sails.helpers.utils.receiveFile('file', this.req);
|
||||||
saveAs: uuid(),
|
|
||||||
maxBytes: null,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return exits.uploadError(error.message); // TODO: add error
|
return exits.uploadError(error.message); // TODO: add error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
const util = require('util');
|
|
||||||
const rimraf = require('rimraf');
|
const rimraf = require('rimraf');
|
||||||
const { v4: uuid } = require('uuid');
|
|
||||||
|
|
||||||
const Errors = {
|
const Errors = {
|
||||||
USER_NOT_FOUND: {
|
USER_NOT_FOUND: {
|
||||||
@@ -54,16 +52,9 @@ module.exports = {
|
|||||||
user = currentUser;
|
user = currentUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
const upload = util.promisify((options, callback) =>
|
|
||||||
this.req.file('file').upload(options, (error, files) => callback(error, files)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let files;
|
let files;
|
||||||
try {
|
try {
|
||||||
files = await upload({
|
files = await sails.helpers.utils.receiveFile('file', this.req);
|
||||||
saveAs: uuid(),
|
|
||||||
maxBytes: null,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return exits.uploadError(error.message); // TODO: add error
|
return exits.uploadError(error.message); // TODO: add error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ module.exports = {
|
|||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
invalidCodeOrNonce: {},
|
invalidCodeOrNonce: {},
|
||||||
invalidUserinfoSignature: {},
|
invalidUserinfoConfiguration: {},
|
||||||
missingValues: {},
|
missingValues: {},
|
||||||
emailAlreadyInUse: {},
|
emailAlreadyInUse: {},
|
||||||
usernameAlreadyInUse: {},
|
usernameAlreadyInUse: {},
|
||||||
@@ -21,9 +21,9 @@ module.exports = {
|
|||||||
async fn(inputs) {
|
async fn(inputs) {
|
||||||
const client = sails.hooks.oidc.getClient();
|
const client = sails.hooks.oidc.getClient();
|
||||||
|
|
||||||
let userInfo;
|
let tokenSet;
|
||||||
try {
|
try {
|
||||||
const tokenSet = await client.callback(
|
tokenSet = await client.callback(
|
||||||
sails.config.custom.oidcRedirectUri,
|
sails.config.custom.oidcRedirectUri,
|
||||||
{
|
{
|
||||||
iss: sails.config.custom.oidcIssuer,
|
iss: sails.config.custom.oidcIssuer,
|
||||||
@@ -33,23 +33,36 @@ module.exports = {
|
|||||||
nonce: inputs.nonce,
|
nonce: inputs.nonce,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
userInfo = await client.userinfo(tokenSet);
|
} catch (error) {
|
||||||
} catch (e) {
|
sails.log.warn(`Error while exchanging OIDC code: ${error}`);
|
||||||
if (
|
|
||||||
e instanceof SyntaxError &&
|
|
||||||
e.message.includes('Unexpected token e in JSON at position 0')
|
|
||||||
) {
|
|
||||||
sails.log.warn('Error while exchanging OIDC code: userinfo response is signed');
|
|
||||||
throw 'invalidUserinfoSignature';
|
|
||||||
}
|
|
||||||
|
|
||||||
sails.log.warn(`Error while exchanging OIDC code: ${e}`);
|
|
||||||
throw 'invalidCodeOrNonce';
|
throw 'invalidCodeOrNonce';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let claims;
|
||||||
|
if (sails.config.custom.oidcClaimsSource === 'id_token') {
|
||||||
|
claims = tokenSet.claims();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
claims = await client.userinfo(tokenSet);
|
||||||
|
} catch (error) {
|
||||||
|
let errorText;
|
||||||
|
if (
|
||||||
|
error instanceof SyntaxError &&
|
||||||
|
error.message.includes('Unexpected token e in JSON at position 0')
|
||||||
|
) {
|
||||||
|
errorText = 'response is signed';
|
||||||
|
} else {
|
||||||
|
errorText = error.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
sails.log.warn(`Error while fetching OIDC userinfo: ${errorText}`);
|
||||||
|
throw 'invalidUserinfoConfiguration';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!userInfo[sails.config.custom.oidcEmailAttribute] ||
|
!claims[sails.config.custom.oidcEmailAttribute] ||
|
||||||
!userInfo[sails.config.custom.oidcNameAttribute]
|
!claims[sails.config.custom.oidcNameAttribute]
|
||||||
) {
|
) {
|
||||||
throw 'missingValues';
|
throw 'missingValues';
|
||||||
}
|
}
|
||||||
@@ -58,23 +71,23 @@ module.exports = {
|
|||||||
if (sails.config.custom.oidcAdminRoles.includes('*')) {
|
if (sails.config.custom.oidcAdminRoles.includes('*')) {
|
||||||
isAdmin = true;
|
isAdmin = true;
|
||||||
} else {
|
} else {
|
||||||
const roles = userInfo[sails.config.custom.oidcRolesAttribute];
|
const roles = claims[sails.config.custom.oidcRolesAttribute];
|
||||||
if (Array.isArray(roles)) {
|
if (Array.isArray(roles)) {
|
||||||
// Use a Set here to avoid quadratic time complexity
|
// Use a Set here to avoid quadratic time complexity
|
||||||
const userRoles = new Set(userInfo[sails.config.custom.oidcRolesAttribute]);
|
const userRoles = new Set(claims[sails.config.custom.oidcRolesAttribute]);
|
||||||
isAdmin = sails.config.custom.oidcAdminRoles.findIndex((role) => userRoles.has(role)) > -1;
|
isAdmin = sails.config.custom.oidcAdminRoles.findIndex((role) => userRoles.has(role)) > -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = {
|
const values = {
|
||||||
isAdmin,
|
isAdmin,
|
||||||
email: userInfo[sails.config.custom.oidcEmailAttribute],
|
email: claims[sails.config.custom.oidcEmailAttribute],
|
||||||
isSso: true,
|
isSso: true,
|
||||||
name: userInfo[sails.config.custom.oidcNameAttribute],
|
name: claims[sails.config.custom.oidcNameAttribute],
|
||||||
subscribeToOwnCards: false,
|
subscribeToOwnCards: false,
|
||||||
};
|
};
|
||||||
if (!sails.config.custom.oidcIgnoreUsername) {
|
if (!sails.config.custom.oidcIgnoreUsername) {
|
||||||
values.username = userInfo[sails.config.custom.oidcUsernameAttribute];
|
values.username = claims[sails.config.custom.oidcUsernameAttribute];
|
||||||
}
|
}
|
||||||
|
|
||||||
let user;
|
let user;
|
||||||
@@ -84,7 +97,7 @@ module.exports = {
|
|||||||
// concurrently with logging in via OIDC.
|
// concurrently with logging in via OIDC.
|
||||||
let identityProviderUser = await IdentityProviderUser.findOne({
|
let identityProviderUser = await IdentityProviderUser.findOne({
|
||||||
issuer: sails.config.custom.oidcIssuer,
|
issuer: sails.config.custom.oidcIssuer,
|
||||||
sub: userInfo.sub,
|
sub: claims.sub,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (identityProviderUser) {
|
if (identityProviderUser) {
|
||||||
@@ -108,7 +121,7 @@ module.exports = {
|
|||||||
identityProviderUser = await IdentityProviderUser.create({
|
identityProviderUser = await IdentityProviderUser.create({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
issuer: sails.config.custom.oidcIssuer,
|
issuer: sails.config.custom.oidcIssuer,
|
||||||
sub: userInfo.sub,
|
sub: claims.sub,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
41
server/api/helpers/utils/receive-file.js
Normal file
41
server/api/helpers/utils/receive-file.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
const util = require('util');
|
||||||
|
const { v4: uuid } = require('uuid');
|
||||||
|
|
||||||
|
async function doUpload(paramName, req, options) {
|
||||||
|
const uploadOptions = {
|
||||||
|
...options,
|
||||||
|
dirname: options.dirname || sails.config.custom.fileUploadTmpDir,
|
||||||
|
};
|
||||||
|
const upload = util.promisify((opts, callback) => {
|
||||||
|
return req.file(paramName).upload(opts, (error, files) => callback(error, files));
|
||||||
|
});
|
||||||
|
return upload(uploadOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
friendlyName: 'Receive uploaded file from request',
|
||||||
|
description:
|
||||||
|
"Store a file uploaded from a MIME-multipart request part. The request part name must be 'file'; the resulting file will have a unique UUID-based name with the same extension.",
|
||||||
|
inputs: {
|
||||||
|
paramName: {
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'The MIME multi-part parameter containing the file to receive.',
|
||||||
|
},
|
||||||
|
req: {
|
||||||
|
type: 'ref',
|
||||||
|
required: true,
|
||||||
|
description: 'The request to receive the file from.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
fn: async function modFn(inputs, exits) {
|
||||||
|
exits.success(
|
||||||
|
await doUpload(inputs.paramName, inputs.req, {
|
||||||
|
saveAs: uuid(),
|
||||||
|
dirname: sails.config.custom.fileUploadTmpDir,
|
||||||
|
maxBytes: null,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -130,8 +130,8 @@ async function sendWebhook(webhook, event, data, user) {
|
|||||||
`Webhook ${webhook.url} failed with status ${response.status} and message: ${message}`,
|
`Webhook ${webhook.url} failed with status ${response.status} and message: ${message}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
sails.log.error(`Webhook ${webhook.url} failed with error message: ${e.message}`);
|
sails.log.error(`Webhook ${webhook.url} failed with error: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const LANGUAGES = [
|
const LANGUAGES = [
|
||||||
|
'ar-YE',
|
||||||
'bg-BG',
|
'bg-BG',
|
||||||
'cs-CZ',
|
'cs-CZ',
|
||||||
'da-DK',
|
'da-DK',
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ module.exports.custom = {
|
|||||||
|
|
||||||
tokenExpiresIn: parseInt(process.env.TOKEN_EXPIRES_IN, 10) || 365,
|
tokenExpiresIn: parseInt(process.env.TOKEN_EXPIRES_IN, 10) || 365,
|
||||||
|
|
||||||
|
// Location to receive uploaded files in. Default (non-string value) is a Sails-specific location.
|
||||||
|
fileUploadTmpDir: null,
|
||||||
|
|
||||||
userAvatarsPath: path.join(sails.config.paths.public, 'user-avatars'),
|
userAvatarsPath: path.join(sails.config.paths.public, 'user-avatars'),
|
||||||
userAvatarsUrl: `${process.env.BASE_URL}/user-avatars`,
|
userAvatarsUrl: `${process.env.BASE_URL}/user-avatars`,
|
||||||
|
|
||||||
@@ -52,6 +55,7 @@ module.exports.custom = {
|
|||||||
oidcResponseMode: process.env.OIDC_RESPONSE_MODE || 'fragment',
|
oidcResponseMode: process.env.OIDC_RESPONSE_MODE || 'fragment',
|
||||||
oidcUseDefaultResponseMode: process.env.OIDC_USE_DEFAULT_RESPONSE_MODE === 'true',
|
oidcUseDefaultResponseMode: process.env.OIDC_USE_DEFAULT_RESPONSE_MODE === 'true',
|
||||||
oidcAdminRoles: process.env.OIDC_ADMIN_ROLES ? process.env.OIDC_ADMIN_ROLES.split(',') : [],
|
oidcAdminRoles: process.env.OIDC_ADMIN_ROLES ? process.env.OIDC_ADMIN_ROLES.split(',') : [],
|
||||||
|
oidcClaimsSource: process.env.OIDC_CLAIMS_SOURCE || 'userinfo',
|
||||||
oidcEmailAttribute: process.env.OIDC_EMAIL_ATTRIBUTE || 'email',
|
oidcEmailAttribute: process.env.OIDC_EMAIL_ATTRIBUTE || 'email',
|
||||||
oidcNameAttribute: process.env.OIDC_NAME_ATTRIBUTE || 'name',
|
oidcNameAttribute: process.env.OIDC_NAME_ATTRIBUTE || 'name',
|
||||||
oidcUsernameAttribute: process.env.OIDC_USERNAME_ATTRIBUTE || 'preferred_username',
|
oidcUsernameAttribute: process.env.OIDC_USERNAME_ATTRIBUTE || 'preferred_username',
|
||||||
|
|||||||
@@ -1,3 +1,56 @@
|
|||||||
|
const serveStatic = require('serve-static');
|
||||||
|
const sails = require('sails');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Remove prefix from urlPath, assuming completely matches a subpath of
|
||||||
|
// urlPath. The result preserves query params and fragment if present
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// '/foo', '/foo/bar' -> '/bar'
|
||||||
|
// '/foo', '/foo' -> '/'
|
||||||
|
// '/foo', '/foo?baz=bux' -> '/?baz=bux'
|
||||||
|
// '/foo', '/foobar' -> '/foobar'
|
||||||
|
function removeRoutePrefix(prefix, urlPath) {
|
||||||
|
if (urlPath.startsWith(prefix)) {
|
||||||
|
const subpath = urlPath.substring(prefix.length);
|
||||||
|
if (subpath.startsWith('/')) {
|
||||||
|
// Prefix matched a complete set of path segments, with a valid path
|
||||||
|
// remaining.
|
||||||
|
return subpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subpath.length === 0 || subpath.startsWith('?') || subpath.startsWith('#')) {
|
||||||
|
// Prefix matched a complete set of path segments, but there is no path
|
||||||
|
// remaining. Add '/'.
|
||||||
|
return `/${subpath}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either the prefix didn't match at all, or it wasn't a complete path match
|
||||||
|
// (e.g. we don't want to treat '/foo' as a prefix of '/foobar'). Leave the
|
||||||
|
// path as-is.
|
||||||
|
return urlPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function staticDirServer(prefix, dirFn) {
|
||||||
|
return function handleReq(req, res, next) {
|
||||||
|
// Custom config properties are not available when the routes config is
|
||||||
|
// loaded, so resolve the target value just before serving the request.
|
||||||
|
const dir = dirFn();
|
||||||
|
const staticServer = serveStatic(dir, { index: false });
|
||||||
|
|
||||||
|
const reqPath = req.url;
|
||||||
|
if (reqPath.startsWith(prefix)) {
|
||||||
|
// serve-static treats the request url as a sub-path of
|
||||||
|
// static root; remove the leading route prefix so the static root
|
||||||
|
// doesn't have to include the prefix as a subdirectory.
|
||||||
|
req.url = removeRoutePrefix(prefix, req.url);
|
||||||
|
return staticServer(req, res, next);
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route Mappings
|
* Route Mappings
|
||||||
* (sails.config.routes)
|
* (sails.config.routes)
|
||||||
@@ -81,6 +134,18 @@ module.exports.routes = {
|
|||||||
'GET /api/notifications/:id': 'notifications/show',
|
'GET /api/notifications/:id': 'notifications/show',
|
||||||
'PATCH /api/notifications/:ids': 'notifications/update',
|
'PATCH /api/notifications/:ids': 'notifications/update',
|
||||||
|
|
||||||
|
'GET /user-avatars/*': {
|
||||||
|
fn: staticDirServer('/user-avatars', () => path.resolve(sails.config.custom.userAvatarsPath)),
|
||||||
|
skipAssets: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
'GET /project-background-images/*': {
|
||||||
|
fn: staticDirServer('/project-background-images', () =>
|
||||||
|
path.resolve(sails.config.custom.projectBackgroundImagesPath),
|
||||||
|
),
|
||||||
|
skipAssets: false,
|
||||||
|
},
|
||||||
|
|
||||||
'GET /attachments/:id/download/:filename': {
|
'GET /attachments/:id/download/:filename': {
|
||||||
action: 'attachments/download',
|
action: 'attachments/download',
|
||||||
skipAssets: false,
|
skipAssets: false,
|
||||||
|
|||||||
13930
server/package-lock.json
generated
13930
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -43,6 +43,7 @@
|
|||||||
"sails-hook-orm": "^4.0.3",
|
"sails-hook-orm": "^4.0.3",
|
||||||
"sails-hook-sockets": "^3.0.1",
|
"sails-hook-sockets": "^3.0.1",
|
||||||
"sails-postgresql": "^5.0.1",
|
"sails-postgresql": "^5.0.1",
|
||||||
|
"serve-static": "^1.13.1",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"stream-to-array": "^2.3.0",
|
"stream-to-array": "^2.3.0",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ const winston = require('winston');
|
|||||||
*/
|
*/
|
||||||
const defaultLogTimestampFormat = 'YYYY-MM-DD HH:mm:ss';
|
const defaultLogTimestampFormat = 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
|
||||||
const logfile = `${process.cwd()}/logs/planka.log`;
|
const logfile =
|
||||||
|
'LOG_FILE' in process.env ? process.env.LOG_FILE : `${process.cwd()}/logs/planka.log`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log level for both console and file log sinks.
|
* Log level for both console and file log sinks.
|
||||||
|
|||||||
Reference in New Issue
Block a user