mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-24 09:28:31 +00:00
feat: add user avatar (#2920)
* feat: add user avatar * update: @nest/platform-express from 8.0.0 to 8.4.4 * add avatar_id in login response * add user avatar upload in frontend * align cross divider with layout icons' * generate nest model - extensions * cleanup * fix tests * reduce the avatar size on homepage * fix review comments * import Express * add blob to csp
This commit is contained in:
parent
ed43ca844a
commit
5dbe795d73
39 changed files with 1493 additions and 387 deletions
|
|
@ -1175,6 +1175,7 @@ class Editor extends React.Component {
|
|||
updatePresence={this.props.updatePresence}
|
||||
editingVersionId={this.state?.editingVersion?.id}
|
||||
self={this.props.self}
|
||||
othersOnSameVersion={this.props.othersOnSameVersion}
|
||||
/>
|
||||
)}
|
||||
{editingVersion && (
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
/* eslint-disable import/no-unresolved */
|
||||
import React from 'react';
|
||||
import Avatar from '@/_ui/Avatar';
|
||||
import { useOthers } from 'y-presence';
|
||||
|
||||
const MAX_DISPLAY_USERS = 3;
|
||||
const RealtimeAvatars = ({ self, updatePresence, editingVersionId }) => {
|
||||
const others = useOthers();
|
||||
const othersOnSameVersion = others.filter((other) => other?.presence?.editingVersionId === editingVersionId);
|
||||
|
||||
const RealtimeAvatars = ({ self, othersOnSameVersion, updatePresence, editingVersionId }) => {
|
||||
React.useEffect(() => {
|
||||
updatePresence({ editingVersionId });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -26,6 +22,7 @@ const RealtimeAvatars = ({ self, updatePresence, editingVersionId }) => {
|
|||
borderColor={self?.presence?.color}
|
||||
title={getAvatarTitle(self?.presence)}
|
||||
text={getAvatarText(self?.presence)}
|
||||
image={self?.presence?.image}
|
||||
/>
|
||||
)}
|
||||
{othersOnSameVersion.slice(0, MAX_DISPLAY_USERS).map(({ id, presence }) => {
|
||||
|
|
@ -35,6 +32,7 @@ const RealtimeAvatars = ({ self, updatePresence, editingVersionId }) => {
|
|||
borderColor={presence.color}
|
||||
title={getAvatarTitle(presence)}
|
||||
text={getAvatarText(presence)}
|
||||
image={presence?.image}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import { useOthers, useSelf } from 'y-presence';
|
|||
import { xorWith, isEqual } from 'lodash';
|
||||
import { Editor } from '@/Editor';
|
||||
import { USER_COLORS } from '@/_helpers/constants';
|
||||
import { userService } from '@/_services';
|
||||
|
||||
const RealtimeCursors = (props) => {
|
||||
const currentUser = JSON.parse(localStorage.getItem('currentUser'));
|
||||
|
||||
const others = useOthers();
|
||||
|
||||
const unavailableColors = others.map((other) => other?.presence?.color);
|
||||
|
|
@ -23,6 +23,19 @@ const RealtimeCursors = (props) => {
|
|||
color: availableColors[Math.floor(Math.random() * availableColors.length)],
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
async function fetchAvatar() {
|
||||
const blob = await userService.getAvatar(currentUser.avatar_id);
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = (e) => {
|
||||
updatePresence({ image: e.target.result });
|
||||
};
|
||||
fileReader.readAsDataURL(blob);
|
||||
}
|
||||
if (currentUser.avatar_id) fetchAvatar();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentUser.avatar_id]);
|
||||
|
||||
const othersOnSameVersion = others.filter(
|
||||
(other) => other?.presence?.editingVersionId === self?.presence.editingVersionId
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ import { toast } from 'react-hot-toast';
|
|||
function SettingsPage(props) {
|
||||
const [firstName, setFirstName] = React.useState(authenticationService.currentUserValue.first_name);
|
||||
const email = authenticationService.currentUserValue.email;
|
||||
const token = authenticationService.currentUserValue.auth_token;
|
||||
const [lastName, setLastName] = React.useState(authenticationService.currentUserValue.last_name);
|
||||
const [currentpassword, setCurrentPassword] = React.useState('');
|
||||
const [newPassword, setNewPassword] = React.useState('');
|
||||
const [updateInProgress, setUpdateInProgress] = React.useState(false);
|
||||
const [passwordChangeInProgress, setPasswordChangeInProgress] = React.useState(false);
|
||||
const [selectedFile, setSelectedFile] = React.useState(null);
|
||||
|
||||
const updateDetails = async () => {
|
||||
if (!firstName || !lastName) {
|
||||
|
|
@ -20,12 +22,25 @@ function SettingsPage(props) {
|
|||
return;
|
||||
}
|
||||
setUpdateInProgress(true);
|
||||
const updatedDetails = await userService.updateCurrentUser(firstName, lastName);
|
||||
authenticationService.updateCurrentUserDetails(updatedDetails);
|
||||
toast.success('Details updated!', {
|
||||
duration: 3000,
|
||||
});
|
||||
setUpdateInProgress(false);
|
||||
try {
|
||||
const updatedDetails = await userService.updateCurrentUser(firstName, lastName);
|
||||
authenticationService.updateCurrentUserDetails(updatedDetails);
|
||||
|
||||
if (selectedFile) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile);
|
||||
const avatarData = await userService.updateAvatar(formData, token);
|
||||
authenticationService.updateCurrentUserDetails({ avatar_id: avatarData.id });
|
||||
}
|
||||
|
||||
toast.success('Details updated!', {
|
||||
duration: 3000,
|
||||
});
|
||||
setUpdateInProgress(false);
|
||||
} catch (error) {
|
||||
toast.error('Something went wrong');
|
||||
setUpdateInProgress(false);
|
||||
}
|
||||
};
|
||||
|
||||
const changePassword = async () => {
|
||||
|
|
@ -111,26 +126,44 @@ function SettingsPage(props) {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-6">
|
||||
<div className="mb-3">
|
||||
<label className="form-label" data-cy="email-label">
|
||||
Email{' '}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
name="email"
|
||||
value={email}
|
||||
readOnly
|
||||
disabled
|
||||
data-cy="email-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<div className="mb-3">
|
||||
<label className="form-label" data-cy="email-label">
|
||||
Email{' '}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
name="email"
|
||||
value={email}
|
||||
readOnly
|
||||
disabled
|
||||
data-cy="email-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="mb-3">
|
||||
<div className="form-label">Avatar</div>
|
||||
<input
|
||||
onChange={(e) => {
|
||||
const file = e.target.files[0];
|
||||
if (Math.round(file.size / 1024) > 2048) {
|
||||
toast.error('File size cannot exceed more than 2MB');
|
||||
e.target.value = null;
|
||||
} else {
|
||||
setSelectedFile(file);
|
||||
}
|
||||
}}
|
||||
accept="image/*"
|
||||
type="file"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="#"
|
||||
className={'btn btn-primary' + (updateInProgress ? ' btn-loading' : '')}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { authenticationService, userService } from '@/_services';
|
||||
import { history } from '@/_helpers';
|
||||
import { DarkModeToggle } from './DarkModeToggle';
|
||||
|
||||
|
|
@ -10,12 +10,26 @@ import { Organization } from './Organization';
|
|||
export const Header = function Header({ switchDarkMode, darkMode }) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [pathName, setPathName] = useState(document.location.pathname);
|
||||
const [avatar, setAvatar] = useState();
|
||||
const { first_name, last_name, avatar_id, admin } = authenticationService.currentUserValue;
|
||||
|
||||
useEffect(() => {
|
||||
setPathName(document.location.pathname);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [document.location.pathname]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
async function fetchAvatar() {
|
||||
const blob = await userService.getAvatar(avatar_id);
|
||||
setAvatar(URL.createObjectURL(blob));
|
||||
}
|
||||
if (avatar_id) fetchAvatar();
|
||||
|
||||
() => avatar && URL.revokeObjectURL(avatar);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [avatar_id]);
|
||||
|
||||
function logout() {
|
||||
authenticationService.logout();
|
||||
history.push('/login');
|
||||
|
|
@ -25,8 +39,6 @@ export const Header = function Header({ switchDarkMode, darkMode }) {
|
|||
history.push('/settings');
|
||||
}
|
||||
|
||||
const { first_name, last_name, admin } = authenticationService.currentUserValue;
|
||||
|
||||
return (
|
||||
<header className="navbar tabbed-navbar navbar-expand-md navbar-light d-print-none">
|
||||
<div className="container-xl">
|
||||
|
|
@ -55,10 +67,19 @@ export const Header = function Header({ switchDarkMode, darkMode }) {
|
|||
data-testid="userAvatarHeader"
|
||||
>
|
||||
<div className="d-xl-block" data-cy="user-menu">
|
||||
<span className="avatar bg-secondary-lt">
|
||||
{first_name ? first_name[0] : ''}
|
||||
{last_name ? last_name[0] : ''}
|
||||
</span>
|
||||
{avatar_id ? (
|
||||
<span
|
||||
className="avatar avatar-sm"
|
||||
style={{
|
||||
backgroundImage: `url(${avatar})`,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span className="avatar bg-secondary-lt">
|
||||
{first_name ? first_name[0] : ''}
|
||||
{last_name ? last_name[0] : ''}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
<div className="dropdown-menu dropdown-menu-end dropdown-menu-arrow end-0" data-cy="dropdown-menu">
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ export const userService = {
|
|||
updateCurrentUser,
|
||||
changePassword,
|
||||
acceptInvite,
|
||||
getAvatar,
|
||||
updateAvatar,
|
||||
};
|
||||
|
||||
function getAll() {
|
||||
|
|
@ -16,6 +18,24 @@ function getAll() {
|
|||
return fetch(`${config.apiUrl}/users`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function getAvatar(id) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader() };
|
||||
return fetch(`${config.apiUrl}/files/${id}`, requestOptions)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => blob);
|
||||
}
|
||||
|
||||
function updateAvatar(formData, token) {
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
return fetch(`${config.apiUrl}/users/avatar`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function createUser(first_name, last_name, email, role) {
|
||||
const body = {
|
||||
first_name,
|
||||
|
|
|
|||
|
|
@ -3932,7 +3932,7 @@ input[type="text"] {
|
|||
.close-icon {
|
||||
position: fixed;
|
||||
top: 84px;
|
||||
right: 0;
|
||||
right: 3px;
|
||||
width: 60px;
|
||||
height: 22;
|
||||
border-bottom: 1px solid #e7eaef;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import React from 'react';
|
||||
|
||||
const Avatar = ({ text, title = '', borderColor = '' }) => {
|
||||
const Avatar = ({ text, image, title = '', borderColor = '' }) => {
|
||||
return (
|
||||
<span
|
||||
data-tip={title}
|
||||
style={{ border: `1.5px solid ${borderColor}` }}
|
||||
style={{ border: `1.5px solid ${borderColor}`, ...(image ? { backgroundImage: `url(${image})` } : {}) }}
|
||||
className="avatar avatar-sm avatar-rounded animation-fade"
|
||||
>
|
||||
{text}
|
||||
{!image && text}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
471
package-lock.json
generated
471
package-lock.json
generated
|
|
@ -1,11 +1,12 @@
|
|||
{
|
||||
"name": "tooljet",
|
||||
"name": "ToolJet",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@nestjs/cli": "^8.1.0",
|
||||
"@nestjs/mapped-types": "*",
|
||||
"cypress": "^8.3.1",
|
||||
"ts-node": "^10.1.0"
|
||||
},
|
||||
|
|
@ -78,6 +79,17 @@
|
|||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||
},
|
||||
"node_modules/@angular-devkit/core/node_modules/rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/core/node_modules/source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
|
|
@ -86,6 +98,11 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/core/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics": {
|
||||
"version": "12.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-12.1.3.tgz",
|
||||
|
|
@ -146,6 +163,38 @@
|
|||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics-cli/node_modules/rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics-cli/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics/node_modules/rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
|
||||
|
|
@ -932,6 +981,59 @@
|
|||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/common": {
|
||||
"version": "8.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-8.4.5.tgz",
|
||||
"integrity": "sha512-DL30hLtcmosOWGRFrU1YYB59k+7FGX82sbq2QiXLsEXuSig8ZzFm8LR+tD8CX+aKabU9t1GGc18HLjFud/3sww==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"axios": "0.27.2",
|
||||
"iterare": "1.2.1",
|
||||
"tslib": "2.4.0",
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/nest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"cache-manager": "*",
|
||||
"class-transformer": "*",
|
||||
"class-validator": "*",
|
||||
"reflect-metadata": "^0.1.12",
|
||||
"rxjs": "^7.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"cache-manager": {
|
||||
"optional": true
|
||||
},
|
||||
"class-transformer": {
|
||||
"optional": true
|
||||
},
|
||||
"class-validator": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/mapped-types": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-1.0.1.tgz",
|
||||
"integrity": "sha512-NFvofzSinp00j5rzUd4tf+xi9od6383iY0JP7o0Bnu1fuItAUkWBgc4EKuIQ3D+c2QI3i9pG1kDWAeY27EMGtg==",
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^7.0.8 || ^8.0.0",
|
||||
"class-transformer": "^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0",
|
||||
"class-validator": "^0.11.1 || ^0.12.0 || ^0.13.0",
|
||||
"reflect-metadata": "^0.1.12"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"class-transformer": {
|
||||
"optional": true
|
||||
},
|
||||
"class-validator": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/schematics": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-8.0.2.tgz",
|
||||
|
|
@ -1038,6 +1140,17 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/schematics/node_modules/rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/schematics/node_modules/source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
|
|
@ -1046,6 +1159,11 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/schematics/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
|
||||
|
|
@ -1530,6 +1648,30 @@
|
|||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
|
||||
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.9",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-loader": {
|
||||
"version": "8.2.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz",
|
||||
|
|
@ -1779,12 +1921,6 @@
|
|||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/camel-case/node_modules/tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001234",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001234.tgz",
|
||||
|
|
@ -2431,12 +2567,6 @@
|
|||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/dot-case/node_modules/tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ecc-jsbn": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||
|
|
@ -2960,6 +3090,26 @@
|
|||
"integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz",
|
||||
"integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
|
|
@ -3561,6 +3711,22 @@
|
|||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inquirer/node_modules/rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inquirer/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/interpret": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
|
||||
|
|
@ -3738,6 +3904,15 @@
|
|||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
||||
},
|
||||
"node_modules/iterare": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz",
|
||||
"integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-worker": {
|
||||
"version": "27.0.6",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.6.tgz",
|
||||
|
|
@ -4040,6 +4215,22 @@
|
|||
"enquirer": ">= 2.3.0 < 3"
|
||||
}
|
||||
},
|
||||
"node_modules/listr2/node_modules/rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/listr2/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/loader-runner": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
|
||||
|
|
@ -4177,12 +4368,6 @@
|
|||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/lower-case/node_modules/tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
|
|
@ -4321,12 +4506,6 @@
|
|||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/no-case/node_modules/tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-emoji": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz",
|
||||
|
|
@ -4542,12 +4721,6 @@
|
|||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/param-case/node_modules/tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
|
|
@ -4586,12 +4759,6 @@
|
|||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/pascal-case/node_modules/tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/path": {
|
||||
"version": "0.12.7",
|
||||
"resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
|
||||
|
|
@ -4828,6 +4995,12 @@
|
|||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect-metadata": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||
"integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/regexpp": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
|
||||
|
|
@ -4975,14 +5148,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
|
||||
"integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=2.0.0"
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
|
|
@ -5581,9 +5752,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
|
|
@ -6100,10 +6271,23 @@
|
|||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -6115,6 +6299,21 @@
|
|||
"@angular-devkit/core": "12.1.3",
|
||||
"ora": "5.4.1",
|
||||
"rxjs": "6.6.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@angular-devkit/schematics-cli": {
|
||||
|
|
@ -6150,6 +6349,19 @@
|
|||
"strip-ansi": "^6.0.0",
|
||||
"through": "^2.3.6"
|
||||
}
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -6817,6 +7029,24 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@nestjs/common": {
|
||||
"version": "8.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-8.4.5.tgz",
|
||||
"integrity": "sha512-DL30hLtcmosOWGRFrU1YYB59k+7FGX82sbq2QiXLsEXuSig8ZzFm8LR+tD8CX+aKabU9t1GGc18HLjFud/3sww==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"axios": "0.27.2",
|
||||
"iterare": "1.2.1",
|
||||
"tslib": "2.4.0",
|
||||
"uuid": "8.3.2"
|
||||
}
|
||||
},
|
||||
"@nestjs/mapped-types": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-1.0.1.tgz",
|
||||
"integrity": "sha512-NFvofzSinp00j5rzUd4tf+xi9od6383iY0JP7o0Bnu1fuItAUkWBgc4EKuIQ3D+c2QI3i9pG1kDWAeY27EMGtg==",
|
||||
"requires": {}
|
||||
},
|
||||
"@nestjs/schematics": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-8.0.2.tgz",
|
||||
|
|
@ -6892,10 +7122,23 @@
|
|||
"wcwidth": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -7301,6 +7544,29 @@
|
|||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
|
||||
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.9",
|
||||
"form-data": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-loader": {
|
||||
"version": "8.2.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz",
|
||||
|
|
@ -7470,14 +7736,6 @@
|
|||
"requires": {
|
||||
"pascal-case": "^3.1.2",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
|
|
@ -7964,14 +8222,6 @@
|
|||
"requires": {
|
||||
"no-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"ecc-jsbn": {
|
||||
|
|
@ -8382,6 +8632,12 @@
|
|||
"integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==",
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz",
|
||||
"integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==",
|
||||
"peer": true
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
|
|
@ -8802,6 +9058,21 @@
|
|||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"through": "^2.3.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"interpret": {
|
||||
|
|
@ -8924,6 +9195,12 @@
|
|||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
||||
},
|
||||
"iterare": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz",
|
||||
"integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==",
|
||||
"peer": true
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "27.0.6",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.6.tgz",
|
||||
|
|
@ -9154,6 +9431,21 @@
|
|||
"rxjs": "^6.6.7",
|
||||
"through": "^2.3.8",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"loader-runner": {
|
||||
|
|
@ -9263,14 +9555,6 @@
|
|||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"lru-cache": {
|
||||
|
|
@ -9382,14 +9666,6 @@
|
|||
"requires": {
|
||||
"lower-case": "^2.0.2",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-emoji": {
|
||||
|
|
@ -9547,14 +9823,6 @@
|
|||
"requires": {
|
||||
"dot-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"parent-module": {
|
||||
|
|
@ -9584,14 +9852,6 @@
|
|||
"requires": {
|
||||
"no-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"path": {
|
||||
|
|
@ -9777,6 +10037,12 @@
|
|||
"resolve": "^1.1.6"
|
||||
}
|
||||
},
|
||||
"reflect-metadata": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||
"integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==",
|
||||
"peer": true
|
||||
},
|
||||
"regexpp": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
|
||||
|
|
@ -9886,11 +10152,12 @@
|
|||
"integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ=="
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.6.7",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
|
||||
"integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
|
|
@ -10316,9 +10583,9 @@
|
|||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@nestjs/cli": "^8.1.0",
|
||||
"@nestjs/mapped-types": "*",
|
||||
"cypress": "^8.3.1",
|
||||
"ts-node": "^10.1.0"
|
||||
},
|
||||
|
|
|
|||
18
server/migrations/1651048832554-AddAvatarIdToUser.ts
Normal file
18
server/migrations/1651048832554-AddAvatarIdToUser.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
|
||||
|
||||
export class AddAvatarToUser1651048832555 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.addColumn(
|
||||
'users',
|
||||
new TableColumn({
|
||||
name: 'avatar_id',
|
||||
type: 'uuid',
|
||||
isNullable: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropColumn('users', 'avatar_id');
|
||||
}
|
||||
}
|
||||
31
server/migrations/1651056032050-CreateFiles.ts
Normal file
31
server/migrations/1651056032050-CreateFiles.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
|
||||
|
||||
export class CreateFiles1651056032050 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: 'files',
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'uuid',
|
||||
isGenerated: true,
|
||||
default: 'gen_random_uuid()',
|
||||
isPrimary: true,
|
||||
},
|
||||
{
|
||||
name: 'data',
|
||||
type: 'bytea',
|
||||
},
|
||||
{
|
||||
name: 'filename',
|
||||
type: 'varchar',
|
||||
},
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {}
|
||||
}
|
||||
883
server/package-lock.json
generated
883
server/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -39,7 +39,7 @@
|
|||
"@nestjs/jwt": "^8.0.0",
|
||||
"@nestjs/mapped-types": "^1.0.1",
|
||||
"@nestjs/passport": "^8.2.1",
|
||||
"@nestjs/platform-express": "^8.0.0",
|
||||
"@nestjs/platform-express": "^8.4.4",
|
||||
"@nestjs/platform-ws": "^8.0.10",
|
||||
"@nestjs/serve-static": "^2.2.2",
|
||||
"@nestjs/typeorm": "^8.0.0",
|
||||
|
|
@ -67,8 +67,8 @@
|
|||
"passport": "^0.4.1",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"pg": "^8.7.1",
|
||||
"preview-email": "^3.0.4",
|
||||
"pino-pretty": "^6.0.0",
|
||||
"preview-email": "^3.0.4",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0",
|
||||
|
|
@ -91,6 +91,7 @@
|
|||
"@types/got": "^9.6.12",
|
||||
"@types/humps": "^2.0.1",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/passport-jwt": "^3.0.6",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { MetaModule } from './modules/meta/meta.module';
|
|||
import { AppController } from './controllers/app.controller';
|
||||
import { AuthModule } from './modules/auth/auth.module';
|
||||
import { UsersModule } from './modules/users/users.module';
|
||||
import { FilesModule } from './modules/files/files.module';
|
||||
import { AppConfigModule } from './modules/app_config/app_config.module';
|
||||
import { AppsModule } from './modules/apps/apps.module';
|
||||
import { FoldersModule } from './modules/folders/folders.module';
|
||||
|
|
@ -77,6 +78,7 @@ const imports = [
|
|||
MetaModule,
|
||||
LibraryAppModule,
|
||||
GroupPermissionsModule,
|
||||
FilesModule,
|
||||
EventsModule,
|
||||
];
|
||||
|
||||
|
|
|
|||
36
server/src/controllers/files.controller.ts
Normal file
36
server/src/controllers/files.controller.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
UseInterceptors,
|
||||
ClassSerializerInterceptor,
|
||||
Res,
|
||||
StreamableFile,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { Readable } from 'stream';
|
||||
import { Response } from 'express';
|
||||
import { FilesService } from '../services/files.service';
|
||||
import { JwtAuthGuard } from 'src/modules/auth/jwt-auth.guard';
|
||||
|
||||
@Controller('files')
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
export class FilesController {
|
||||
constructor(private readonly filesService: FilesService) {}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async show(@Param('id') id: string, @Res({ passthrough: true }) response: Response) {
|
||||
const file = await this.filesService.findOne(id);
|
||||
|
||||
const stream = Readable.from(file.data);
|
||||
|
||||
response.set({
|
||||
'Content-Disposition': `inline; filename="${file.filename}"`,
|
||||
'Content-Type': 'image',
|
||||
});
|
||||
|
||||
// https://docs.nestjs.com/techniques/streaming-files
|
||||
return new StreamableFile(stream);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import { Body, Controller, Post, Patch, UseGuards } from '@nestjs/common';
|
||||
import { Body, Controller, Post, Patch, UseGuards, UseInterceptors, Req, UploadedFile } from '@nestjs/common';
|
||||
import { Express } from 'express';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { JwtAuthGuard } from 'src/modules/auth/jwt-auth.guard';
|
||||
import { PasswordRevalidateGuard } from 'src/modules/auth/password-revalidate.guard';
|
||||
import { UsersService } from 'src/services/users.service';
|
||||
|
|
@ -37,6 +39,13 @@ export class UsersController {
|
|||
};
|
||||
}
|
||||
|
||||
@Post('avatar')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async addAvatar(@Req() req, @UploadedFile() file: Express.Multer.File) {
|
||||
return this.usersService.addAvatar(req.user.id, file.buffer, file.originalname);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard, PasswordRevalidateGuard)
|
||||
@Patch('change_password')
|
||||
async changePassword(@User() user, @Body('newPassword') newPassword) {
|
||||
|
|
|
|||
9
server/src/dto/create-file.dto.ts
Normal file
9
server/src/dto/create-file.dto.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class CreateFileDto {
|
||||
@IsNotEmpty()
|
||||
data: Uint8Array | Buffer | string;
|
||||
|
||||
@IsNotEmpty()
|
||||
filename: string;
|
||||
}
|
||||
4
server/src/dto/update-file.dto.ts
Normal file
4
server/src/dto/update-file.dto.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateFileDto } from './create-file.dto';
|
||||
|
||||
export class UpdateFileDto extends PartialType(CreateFileDto) {}
|
||||
15
server/src/entities/file.entity.ts
Normal file
15
server/src/entities/file.entity.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity({ name: 'files' })
|
||||
export class File {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id: string;
|
||||
|
||||
@Column()
|
||||
filename: string;
|
||||
|
||||
@Column({
|
||||
type: 'bytea',
|
||||
})
|
||||
data: Uint8Array | Buffer | string;
|
||||
}
|
||||
|
|
@ -10,12 +10,17 @@ import {
|
|||
BaseEntity,
|
||||
ManyToMany,
|
||||
JoinTable,
|
||||
OneToOne,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
} from 'typeorm';
|
||||
import { App } from './app.entity';
|
||||
import { GroupPermission } from './group_permission.entity';
|
||||
const bcrypt = require('bcrypt');
|
||||
import { OrganizationUser } from './organization_user.entity';
|
||||
import { UserGroupPermission } from './user_group_permission.entity';
|
||||
import { File } from './file.entity';
|
||||
import { Organization } from './organization.entity';
|
||||
|
||||
@Entity({ name: 'users' })
|
||||
export class User extends BaseEntity {
|
||||
|
|
@ -39,6 +44,9 @@ export class User extends BaseEntity {
|
|||
@Column()
|
||||
email: string;
|
||||
|
||||
@Column({ name: 'avatar_id', nullable: true, default: null })
|
||||
avatarId?: string;
|
||||
|
||||
@Column({ name: 'invitation_token' })
|
||||
invitationToken: string;
|
||||
|
||||
|
|
@ -63,6 +71,16 @@ export class User extends BaseEntity {
|
|||
@OneToMany(() => OrganizationUser, (organizationUser) => organizationUser.user, { eager: true })
|
||||
organizationUsers: OrganizationUser[];
|
||||
|
||||
@ManyToOne(() => Organization, (organization) => organization.id)
|
||||
@JoinColumn({ name: 'organization_id' })
|
||||
organization: Organization;
|
||||
|
||||
@JoinColumn({ name: 'avatar_id' })
|
||||
@OneToOne(() => File, {
|
||||
nullable: true,
|
||||
})
|
||||
avatar?: File;
|
||||
|
||||
@ManyToMany(() => GroupPermission)
|
||||
@JoinTable({
|
||||
name: 'user_group_permissions',
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ async function bootstrap() {
|
|||
useDefaults: true,
|
||||
directives: {
|
||||
upgradeInsecureRequests: null,
|
||||
'img-src': ['*', 'data:'],
|
||||
'img-src': ['*', 'data:', 'blob:'],
|
||||
'script-src': [
|
||||
'maps.googleapis.com',
|
||||
'apis.google.com',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { App } from '../../entities/app.entity';
|
||||
import { File } from '../../entities/file.entity';
|
||||
import { AppsController } from '../../controllers/apps.controller';
|
||||
import { AppsService } from '../../services/apps.service';
|
||||
import { AppVersion } from '../../../src/entities/app_version.entity';
|
||||
|
|
@ -13,6 +14,7 @@ import { OrganizationUser } from 'src/entities/organization_user.entity';
|
|||
import { UsersService } from '@services/users.service';
|
||||
import { User } from 'src/entities/user.entity';
|
||||
import { Organization } from 'src/entities/organization.entity';
|
||||
import { FilesService } from '@services/files.service';
|
||||
import { FoldersService } from '@services/folders.service';
|
||||
import { Folder } from 'src/entities/folder.entity';
|
||||
import { FolderApp } from 'src/entities/folder_app.entity';
|
||||
|
|
@ -43,6 +45,7 @@ import { Credential } from 'src/entities/credential.entity';
|
|||
AppGroupPermission,
|
||||
UserGroupPermission,
|
||||
Credential,
|
||||
File,
|
||||
]),
|
||||
CaslModule,
|
||||
],
|
||||
|
|
@ -55,6 +58,7 @@ import { Credential } from 'src/entities/credential.entity';
|
|||
DataSourcesService,
|
||||
CredentialsService,
|
||||
EncryptionService,
|
||||
FilesService,
|
||||
],
|
||||
controllers: [AppsController, AppUsersController],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import { OauthService, GoogleOAuthService, GitOAuthService } from '@ee/services/
|
|||
import { OauthController } from '@ee/controllers/oauth.controller';
|
||||
import { GroupPermission } from 'src/entities/group_permission.entity';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
import { File } from 'src/entities/file.entity';
|
||||
import { FilesService } from '@services/files.service';
|
||||
import { SSOConfigs } from 'src/entities/sso_config.entity';
|
||||
import { GroupPermissionsService } from '@services/group_permissions.service';
|
||||
import { AppGroupPermission } from 'src/entities/app_group_permission.entity';
|
||||
|
|
@ -29,6 +31,7 @@ import { EncryptionService } from '@services/encryption.service';
|
|||
PassportModule,
|
||||
TypeOrmModule.forFeature([
|
||||
User,
|
||||
File,
|
||||
Organization,
|
||||
OrganizationUser,
|
||||
GroupPermission,
|
||||
|
|
@ -59,6 +62,7 @@ import { EncryptionService } from '@services/encryption.service';
|
|||
OauthService,
|
||||
GoogleOAuthService,
|
||||
GitOAuthService,
|
||||
FilesService,
|
||||
GroupPermissionsService,
|
||||
EncryptionService,
|
||||
],
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { EmailService } from '@services/email.service';
|
|||
import { OrganizationUsersService } from '@services/organization_users.service';
|
||||
import { UsersService } from '@services/users.service';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
import { File } from 'src/entities/file.entity';
|
||||
import { Organization } from 'src/entities/organization.entity';
|
||||
import { OrganizationUser } from 'src/entities/organization_user.entity';
|
||||
import { User } from 'src/entities/user.entity';
|
||||
|
|
@ -12,12 +13,14 @@ import { ThreadsAbilityFactory } from './abilities/threads-ability.factory';
|
|||
import { CommentsAbilityFactory } from './abilities/comments-ability.factory';
|
||||
import { CaslAbilityFactory } from './casl-ability.factory';
|
||||
import { FoldersAbilityFactory } from './abilities/folders-ability.factory';
|
||||
import { FilesService } from '@services/files.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([User, Organization, OrganizationUser, App])],
|
||||
imports: [TypeOrmModule.forFeature([User, File, Organization, OrganizationUser, App])],
|
||||
providers: [
|
||||
CaslAbilityFactory,
|
||||
OrganizationUsersService,
|
||||
FilesService,
|
||||
UsersService,
|
||||
EmailService,
|
||||
AppsAbilityFactory,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { EncryptionService } from '../../../src/services/encryption.service';
|
|||
import { Credential } from '../../../src/entities/credential.entity';
|
||||
import { DataSourcesService } from '../../../src/services/data_sources.service';
|
||||
import { DataSource } from '../../../src/entities/data_source.entity';
|
||||
import { File } from 'src/entities/file.entity';
|
||||
import { CaslModule } from '../casl/casl.module';
|
||||
import { AppsService } from '@services/apps.service';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
|
|
@ -21,11 +22,13 @@ import { User } from 'src/entities/user.entity';
|
|||
import { OrganizationUser } from 'src/entities/organization_user.entity';
|
||||
import { Organization } from 'src/entities/organization.entity';
|
||||
import { AppImportExportService } from '@services/app_import_export.service';
|
||||
import { FilesService } from '@services/files.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
App,
|
||||
File,
|
||||
AppVersion,
|
||||
AppUser,
|
||||
DataQuery,
|
||||
|
|
@ -48,6 +51,7 @@ import { AppImportExportService } from '@services/app_import_export.service';
|
|||
AppsService,
|
||||
UsersService,
|
||||
AppImportExportService,
|
||||
FilesService,
|
||||
],
|
||||
controllers: [DataQueriesController],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Credential } from '../../../src/entities/credential.entity';
|
|||
import { EncryptionService } from '../../../src/services/encryption.service';
|
||||
import { AppsService } from '@services/apps.service';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
import { File } from 'src/entities/file.entity';
|
||||
import { AppVersion } from 'src/entities/app_version.entity';
|
||||
import { AppUser } from 'src/entities/app_user.entity';
|
||||
import { CaslModule } from '../casl/casl.module';
|
||||
|
|
@ -21,6 +22,7 @@ import { User } from 'src/entities/user.entity';
|
|||
import { OrganizationUser } from 'src/entities/organization_user.entity';
|
||||
import { Organization } from 'src/entities/organization.entity';
|
||||
import { AppImportExportService } from '@services/app_import_export.service';
|
||||
import { FilesService } from '@services/files.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -29,6 +31,7 @@ import { AppImportExportService } from '@services/app_import_export.service';
|
|||
DataQuery,
|
||||
Credential,
|
||||
App,
|
||||
File,
|
||||
AppVersion,
|
||||
AppUser,
|
||||
FolderApp,
|
||||
|
|
@ -48,6 +51,7 @@ import { AppImportExportService } from '@services/app_import_export.service';
|
|||
DataQueriesService,
|
||||
UsersService,
|
||||
AppImportExportService,
|
||||
FilesService,
|
||||
],
|
||||
controllers: [DataSourcesController],
|
||||
})
|
||||
|
|
|
|||
12
server/src/modules/files/files.module.ts
Normal file
12
server/src/modules/files/files.module.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { File } from 'src/entities/file.entity';
|
||||
import { FilesController } from '../../controllers/files.controller';
|
||||
import { FilesService } from '../../services/files.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([File])],
|
||||
controllers: [FilesController],
|
||||
providers: [FilesService],
|
||||
})
|
||||
export class FilesModule {}
|
||||
|
|
@ -5,7 +5,9 @@ import { Folder } from '../../entities/folder.entity';
|
|||
import { FoldersController } from '../../controllers/folders.controller';
|
||||
import { FoldersService } from '../../services/folders.service';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
import { File } from 'src/entities/file.entity';
|
||||
import { UsersService } from '@services/users.service';
|
||||
import { FilesService } from '@services/files.service';
|
||||
import { User } from 'src/entities/user.entity';
|
||||
import { OrganizationUser } from 'src/entities/organization_user.entity';
|
||||
import { Organization } from 'src/entities/organization.entity';
|
||||
|
|
@ -13,7 +15,7 @@ import { CaslModule } from '../casl/casl.module';
|
|||
|
||||
@Module({
|
||||
controllers: [FoldersController],
|
||||
imports: [TypeOrmModule.forFeature([App, Folder, FolderApp, User, OrganizationUser, Organization]), CaslModule],
|
||||
providers: [FoldersService, UsersService],
|
||||
imports: [TypeOrmModule.forFeature([App, File, Folder, FolderApp, User, OrganizationUser, Organization]), CaslModule],
|
||||
providers: [FilesService, FoldersService, UsersService],
|
||||
})
|
||||
export class FoldersModule {}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import { User } from 'src/entities/user.entity';
|
|||
import { OrganizationUser } from 'src/entities/organization_user.entity';
|
||||
import { Organization } from 'src/entities/organization.entity';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
import { File } from 'src/entities/file.entity';
|
||||
import { FilesService } from '@services/files.service';
|
||||
|
||||
@Module({
|
||||
controllers: [GroupPermissionsController],
|
||||
|
|
@ -23,9 +25,10 @@ import { App } from 'src/entities/app.entity';
|
|||
OrganizationUser,
|
||||
Organization,
|
||||
App,
|
||||
File,
|
||||
]),
|
||||
CaslModule,
|
||||
],
|
||||
providers: [GroupPermissionsService, UsersService],
|
||||
providers: [GroupPermissionsService, FilesService, UsersService],
|
||||
})
|
||||
export class GroupPermissionsModule {}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ import { OrganizationUsersController } from '@controllers/organization_users.con
|
|||
import { UsersService } from 'src/services/users.service';
|
||||
import { CaslModule } from '../casl/casl.module';
|
||||
import { EmailService } from '@services/email.service';
|
||||
import { FilesService } from '@services/files.service';
|
||||
import { GroupPermission } from 'src/entities/group_permission.entity';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
import { File } from 'src/entities/file.entity';
|
||||
import { SSOConfigs } from 'src/entities/sso_config.entity';
|
||||
import { AuthService } from '@services/auth.service';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
|
|
@ -27,6 +29,7 @@ import { EncryptionService } from '@services/encryption.service';
|
|||
Organization,
|
||||
OrganizationUser,
|
||||
User,
|
||||
File,
|
||||
GroupPermission,
|
||||
App,
|
||||
SSOConfigs,
|
||||
|
|
@ -51,6 +54,7 @@ import { EncryptionService } from '@services/encryption.service';
|
|||
OrganizationUsersService,
|
||||
UsersService,
|
||||
EmailService,
|
||||
FilesService,
|
||||
AuthService,
|
||||
GroupPermissionsService,
|
||||
EncryptionService,
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ import { UsersService } from '../../services/users.service';
|
|||
import { OrganizationUser } from '../../entities/organization_user.entity';
|
||||
import { Organization } from '../../entities/organization.entity';
|
||||
import { User } from '../../entities/user.entity';
|
||||
import { File } from '../../entities/file.entity';
|
||||
import { UsersController } from 'src/controllers/users.controller';
|
||||
import { OrganizationsModule } from '../organizations/organizations.module';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
import { FilesService } from '@services/files.service';
|
||||
|
||||
@Module({
|
||||
imports: [OrganizationsModule, TypeOrmModule.forFeature([User, Organization, OrganizationUser, App])],
|
||||
providers: [UsersService],
|
||||
imports: [OrganizationsModule, TypeOrmModule.forFeature([User, File, Organization, OrganizationUser, App])],
|
||||
providers: [UsersService, FilesService],
|
||||
controllers: [UsersController],
|
||||
})
|
||||
export class UsersModule {}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ export class AuthService {
|
|||
email: user.email,
|
||||
first_name: user.firstName,
|
||||
last_name: user.lastName,
|
||||
avatar_id: user.avatarId,
|
||||
organizationId: user.organizationId,
|
||||
organization: organization.name,
|
||||
admin: await this.usersService.hasGroup(user, 'admin'),
|
||||
|
|
|
|||
51
server/src/services/files.service.ts
Normal file
51
server/src/services/files.service.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { QueryRunner, Repository } from 'typeorm';
|
||||
import { CreateFileDto } from '../dto/create-file.dto';
|
||||
import { UpdateFileDto } from '../dto/update-file.dto';
|
||||
import { File } from '../entities/file.entity';
|
||||
|
||||
@Injectable()
|
||||
export class FilesService {
|
||||
constructor(
|
||||
@InjectRepository(File)
|
||||
private fileRepository: Repository<File>
|
||||
) {}
|
||||
|
||||
async create(createFileDto: CreateFileDto, queryRunner: QueryRunner) {
|
||||
const newFile = queryRunner.manager.create(File, {
|
||||
filename: createFileDto.filename,
|
||||
data: createFileDto.data,
|
||||
});
|
||||
try {
|
||||
await queryRunner.manager.save(File, newFile);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
return newFile;
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return `This action returns all files`;
|
||||
}
|
||||
|
||||
async findOne(id: string) {
|
||||
const file = await this.fileRepository.findOne(id);
|
||||
if (!file) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
update(id: string, updateFileDto: UpdateFileDto) {
|
||||
return `This action updates a #${id} file`;
|
||||
}
|
||||
|
||||
async remove(id: string, queryRunner: QueryRunner) {
|
||||
const deleteResponse = await queryRunner.manager.delete(File, id);
|
||||
if (!deleteResponse?.affected) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
return deleteResponse;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, InternalServerErrorException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { User } from '../entities/user.entity';
|
||||
import { FilesService } from '../services/files.service';
|
||||
import { Organization } from 'src/entities/organization.entity';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
import { createQueryBuilder, EntityManager, getManager, getRepository, In, Repository } from 'typeorm';
|
||||
import { Connection, createQueryBuilder, EntityManager, getManager, getRepository, In, Repository } from 'typeorm';
|
||||
import { OrganizationUser } from '../entities/organization_user.entity';
|
||||
import { AppGroupPermission } from 'src/entities/app_group_permission.entity';
|
||||
import { UserGroupPermission } from 'src/entities/user_group_permission.entity';
|
||||
|
|
@ -11,12 +12,15 @@ import { GroupPermission } from 'src/entities/group_permission.entity';
|
|||
import { BadRequestException } from '@nestjs/common';
|
||||
import { cleanObject } from 'src/helpers/utils.helper';
|
||||
import { CreateUserDto } from '@dto/user.dto';
|
||||
import { CreateFileDto } from '@dto/create-file.dto';
|
||||
const uuid = require('uuid');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(
|
||||
private readonly filesService: FilesService,
|
||||
private connection: Connection,
|
||||
@InjectRepository(User)
|
||||
private usersRepository: Repository<User>,
|
||||
@InjectRepository(OrganizationUser)
|
||||
|
|
@ -422,6 +426,39 @@ export class UsersService {
|
|||
return !!app && app.organizationId === user.organizationId;
|
||||
}
|
||||
|
||||
async addAvatar(userId: number, imageBuffer: Buffer, filename: string) {
|
||||
const queryRunner = this.connection.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const user = await queryRunner.manager.findOne(User, userId);
|
||||
const currentAvatarId = user.avatarId;
|
||||
const createFileDto = new CreateFileDto();
|
||||
createFileDto.filename = filename;
|
||||
createFileDto.data = imageBuffer;
|
||||
const avatar = await this.filesService.create(createFileDto, queryRunner);
|
||||
|
||||
await queryRunner.manager.update(User, userId, {
|
||||
avatarId: avatar.id,
|
||||
});
|
||||
|
||||
if (currentAvatarId) {
|
||||
await this.filesService.remove(currentAvatarId, queryRunner);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return avatar;
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw new InternalServerErrorException(error);
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
canAnyGroupPerformAction(action: string, permissions: AppGroupPermission[] | GroupPermission[]): boolean {
|
||||
return permissions.some((p) => p[action]);
|
||||
}
|
||||
|
|
|
|||
BIN
server/test/__mocks__/avatar.png
Normal file
BIN
server/test/__mocks__/avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
33
server/test/controllers/files.e2e-spec.ts
Normal file
33
server/test/controllers/files.e2e-spec.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import * as request from 'supertest';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { authHeaderForUser, createFile, clearDB, createUser, createNestAppInstance } from '../test.helper';
|
||||
|
||||
describe('files controller', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
await clearDB();
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await createNestAppInstance();
|
||||
});
|
||||
|
||||
it('should not allow un-authenticated users to fetch a file', async () => {
|
||||
await request(app.getHttpServer()).get('/api/files/2540333b-f6fe-42b7-857c-736f24f9b644').expect(401);
|
||||
});
|
||||
|
||||
it('should allow only authenticated users to fetch a file', async () => {
|
||||
const userData = await createUser(app, { email: 'admin@tooljet.io' });
|
||||
|
||||
const { user } = userData;
|
||||
|
||||
const file = await createFile(app);
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/files/${file.id}`)
|
||||
.set('Authorization', authHeaderForUser(user));
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
});
|
||||
});
|
||||
|
|
@ -5,6 +5,7 @@ import { getManager } from 'typeorm';
|
|||
import { User } from 'src/entities/user.entity';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { OrganizationUser } from 'src/entities/organization_user.entity';
|
||||
const path = require('path');
|
||||
|
||||
describe('users controller', () => {
|
||||
let app: INestApplication;
|
||||
|
|
@ -346,6 +347,22 @@ describe('users controller', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('POST /api/users/avatar', () => {
|
||||
it('should allow users to add a avatar', async () => {
|
||||
const userData = await createUser(app, { email: 'admin@tooljet.io' });
|
||||
|
||||
const { user } = userData;
|
||||
const filePath = path.join(__dirname, '../__mocks__/avatar.png');
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.post('/api/users/avatar')
|
||||
.set('Authorization', authHeaderForUser(user))
|
||||
.attach('file', filePath);
|
||||
|
||||
expect(response.statusCode).toBe(201);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { OrganizationUser } from 'src/entities/organization_user.entity';
|
|||
import { Organization } from 'src/entities/organization.entity';
|
||||
import { User } from 'src/entities/user.entity';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
import { File } from 'src/entities/file.entity';
|
||||
import { INestApplication, ValidationPipe } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { AppModule } from 'src/app.module';
|
||||
|
|
@ -25,6 +26,7 @@ import { AppsModule } from 'src/modules/apps/apps.module';
|
|||
import { LibraryAppCreationService } from '@services/library_app_creation.service';
|
||||
import { createMock, DeepMocked } from '@golevelup/ts-jest';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { CreateFileDto } from '@dto/create-file.dto';
|
||||
|
||||
export async function createNestAppInstance(): Promise<INestApplication> {
|
||||
let app: INestApplication;
|
||||
|
|
@ -443,6 +445,15 @@ export async function createDataQuery(nestApp, { application, kind, dataSource,
|
|||
);
|
||||
}
|
||||
|
||||
export async function createFile(nestApp: any) {
|
||||
let fileRepository: Repository<File>;
|
||||
fileRepository = nestApp.get('FileRepository');
|
||||
const createFileDto = new CreateFileDto();
|
||||
createFileDto.filename = 'testfile';
|
||||
createFileDto.data = Buffer.from([1, 2, 3, 4]);
|
||||
return await fileRepository.save(fileRepository.create(createFileDto));
|
||||
}
|
||||
|
||||
export async function createThread(_nestApp, { appId, x, y, userId, organizationId, appVersionsId }: any) {
|
||||
const threadRepository = new ThreadRepository();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue