ajout de l'upload. Reste un mystérieux problème à régler avec l'extenstion et multer

This commit is contained in:
batche
2022-11-16 19:12:02 +01:00
parent fd61ea56d2
commit f98e1fdb4e
21 changed files with 609 additions and 54 deletions

View File

@@ -9,3 +9,6 @@
!api_back/*.html
!api_back/*.lock
!api_back/.env
!api_back/src/uploads/avatars/default.png
!api_back/*.jpg
!api_back/*.jpeg

View File

@@ -5,6 +5,7 @@ WORKDIR /usr/app
COPY api_back/* .
COPY api_back/.env .env
RUN npm ci
CMD [ "npm", "run", "start:dev" ]

View File

@@ -2207,6 +2207,15 @@
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA=="
},
"node_modules/@types/multer": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz",
"integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==",
"dev": true,
"dependencies": {
"@types/express": "*"
}
},
"node_modules/@types/node": {
"version": "16.11.68",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.68.tgz",

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

View File

@@ -0,0 +1,16 @@
# Installation
> `npm install --save @types/multer`
# Summary
This package contains type definitions for multer (https://github.com/expressjs/multer).
# Details
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/multer.
### Additional Details
* Last updated: Wed, 07 Jul 2021 00:01:45 GMT
* Dependencies: [@types/express](https://npmjs.com/package/@types/express)
* Global values: none
# Credits
These definitions were written by [jt000](https://github.com/jt000), [vilicvane](https://github.com/vilic), [David Broder-Rodgers](https://github.com/DavidBR-SW), [Michael Ledin](https://github.com/mxl), [HyunSeob Lee](https://github.com/hyunseob), [Pierre Tchuente](https://github.com/PierreTchuente), [Oliver Emery](https://github.com/thrymgjol), and [Piotr Błażejewicz](https://github.com/peterblazejewicz).

View File

@@ -0,0 +1,321 @@
// Type definitions for multer 1.4
// Project: https://github.com/expressjs/multer
// Definitions by: jt000 <https://github.com/jt000>
// vilicvane <https://github.com/vilic>
// David Broder-Rodgers <https://github.com/DavidBR-SW>
// Michael Ledin <https://github.com/mxl>
// HyunSeob Lee <https://github.com/hyunseob>
// Pierre Tchuente <https://github.com/PierreTchuente>
// Oliver Emery <https://github.com/thrymgjol>
// Piotr Błażejewicz <https://github.com/peterblazejewicz>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
import { Request, RequestHandler } from 'express';
import { Readable } from 'stream';
declare global {
namespace Express {
namespace Multer {
/** Object containing file metadata and access information. */
interface File {
/** Name of the form field associated with this file. */
fieldname: string;
/** Name of the file on the uploader's computer. */
originalname: string;
/**
* Value of the `Content-Transfer-Encoding` header for this file.
* @deprecated since July 2015
* @see RFC 7578, Section 4.7
*/
encoding: string;
/** Value of the `Content-Type` header for this file. */
mimetype: string;
/** Size of the file in bytes. */
size: number;
/**
* A readable stream of this file. Only available to the `_handleFile`
* callback for custom `StorageEngine`s.
*/
stream: Readable;
/** `DiskStorage` only: Directory to which this file has been uploaded. */
destination: string;
/** `DiskStorage` only: Name of this file within `destination`. */
filename: string;
/** `DiskStorage` only: Full path to the uploaded file. */
path: string;
/** `MemoryStorage` only: A Buffer containing the entire file. */
buffer: Buffer;
}
}
interface Request {
/** `Multer.File` object populated by `single()` middleware. */
file?: Multer.File | undefined;
/**
* Array or dictionary of `Multer.File` object populated by `array()`,
* `fields()`, and `any()` middleware.
*/
files?: {
[fieldname: string]: Multer.File[];
} | Multer.File[] | undefined;
}
}
}
/**
* Returns a Multer instance that provides several methods for generating
* middleware that process files uploaded in `multipart/form-data` format.
*
* The `StorageEngine` specified in `storage` will be used to store files. If
* `storage` is not set and `dest` is, files will be stored in `dest` on the
* local file system with random names. If neither are set, files will be stored
* in memory.
*
* In addition to files, all generated middleware process all text fields in
* the request. For each non-file field, the `Request.body` object will be
* populated with an entry mapping the field name to its string value, or array
* of string values if multiple fields share the same name.
*/
declare function multer(options?: multer.Options): multer.Multer;
declare namespace multer {
/**
* @see {@link https://github.com/expressjs/multer#api}
*/
interface Multer {
/**
* Returns middleware that processes a single file associated with the
* given form field.
*
* The `Request` object will be populated with a `file` object containing
* information about the processed file.
*
* @param fieldName Name of the multipart form field to process.
*/
single(fieldName: string): RequestHandler;
/**
* Returns middleware that processes multiple files sharing the same field
* name.
*
* The `Request` object will be populated with a `files` array containing
* an information object for each processed file.
*
* @param fieldName Shared name of the multipart form fields to process.
* @param maxCount Optional. Maximum number of files to process. (default: Infinity)
* @throws `MulterError('LIMIT_UNEXPECTED_FILE')` if more than `maxCount` files are associated with `fieldName`
*/
array(fieldName: string, maxCount?: number): RequestHandler;
/**
* Returns middleware that processes multiple files associated with the
* given form fields.
*
* The `Request` object will be populated with a `files` object which
* maps each field name to an array of the associated file information
* objects.
*
* @param fields Array of `Field` objects describing multipart form fields to process.
* @throws `MulterError('LIMIT_UNEXPECTED_FILE')` if more than `maxCount` files are associated with `fieldName` for any field.
*/
fields(fields: ReadonlyArray<Field>): RequestHandler;
/**
* Returns middleware that processes all files contained in the multipart
* request.
*
* The `Request` object will be populated with a `files` array containing
* an information object for each processed file.
*/
any(): RequestHandler;
/**
* Returns middleware that accepts only non-file multipart form fields.
*
* @throws `MulterError('LIMIT_UNEXPECTED_FILE')` if any file is encountered.
*/
none(): RequestHandler;
}
/**
* Returns a `StorageEngine` implementation configured to store files on
* the local file system.
*
* A string or function may be specified to determine the destination
* directory, and a function to determine filenames. If no options are set,
* files will be stored in the system's temporary directory with random 32
* character filenames.
*/
function diskStorage(options: DiskStorageOptions): StorageEngine;
/**
* Returns a `StorageEngine` implementation configured to store files in
* memory as `Buffer` objects.
*/
function memoryStorage(): StorageEngine;
type ErrorCode =
| 'LIMIT_PART_COUNT'
| 'LIMIT_FILE_SIZE'
| 'LIMIT_FILE_COUNT'
| 'LIMIT_FIELD_KEY'
| 'LIMIT_FIELD_VALUE'
| 'LIMIT_FIELD_COUNT'
| 'LIMIT_UNEXPECTED_FILE';
class MulterError extends Error {
constructor(code: ErrorCode, field?: string);
/** Name of the MulterError constructor. */
name: string;
/** Identifying error code. */
code: ErrorCode;
/** Descriptive error message. */
message: string;
/** Name of the multipart form field associated with this error. */
field?: string | undefined;
}
/**
* a function to control which files should be uploaded and which should be skipped
* pass a boolean to indicate if the file should be accepted
* pass an error if something goes wrong
*/
interface FileFilterCallback {
(error: Error): void;
(error: null, acceptFile: boolean): void;
}
/** Options for initializing a Multer instance. */
interface Options {
/**
* A `StorageEngine` responsible for processing files uploaded via Multer.
* Takes precedence over `dest`.
*/
storage?: StorageEngine | undefined;
/**
* The destination directory for uploaded files. If `storage` is not set
* and `dest` is, Multer will create a `DiskStorage` instance configured
* to store files at `dest` with random filenames.
*
* Ignored if `storage` is set.
*/
dest?: string | undefined;
/**
* An object specifying various limits on incoming data. This object is
* passed to Busboy directly, and the details of properties can be found
* at https://github.com/mscdex/busboy#busboy-methods.
*/
limits?: {
/** Maximum size of each form field name in bytes. (Default: 100) */
fieldNameSize?: number | undefined;
/** Maximum size of each form field value in bytes. (Default: 1048576) */
fieldSize?: number | undefined;
/** Maximum number of non-file form fields. (Default: Infinity) */
fields?: number | undefined;
/** Maximum size of each file in bytes. (Default: Infinity) */
fileSize?: number | undefined;
/** Maximum number of file fields. (Default: Infinity) */
files?: number | undefined;
/** Maximum number of parts (non-file fields + files). (Default: Infinity) */
parts?: number | undefined;
/** Maximum number of headers. (Default: 2000) */
headerPairs?: number | undefined;
} | undefined;
/** Preserve the full path of the original filename rather than the basename. (Default: false) */
preservePath?: boolean | undefined;
/**
* Optional function to control which files are uploaded. This is called
* for every file that is processed.
*
* @param req The Express `Request` object.
* @param file Object containing information about the processed file.
* @param callback a function to control which files should be uploaded and which should be skipped.
*/
fileFilter?(
req: Request,
file: Express.Multer.File,
callback: FileFilterCallback,
): void;
}
/**
* Implementations of this interface are responsible for storing files
* encountered by Multer and returning information on how to access them
* once stored. Implementations must also provide a method for removing
* files in the event that an error occurs.
*/
interface StorageEngine {
/**
* Store the file described by `file`, then invoke the callback with
* information about the stored file.
*
* File contents are available as a stream via `file.stream`. Information
* passed to the callback will be merged with `file` for subsequent
* middleware.
*
* @param req The Express `Request` object.
* @param file Object with `stream`, `fieldname`, `originalname`, `encoding`, and `mimetype` defined.
* @param callback Callback to specify file information.
*/
_handleFile(
req: Request,
file: Express.Multer.File,
callback: (error?: any, info?: Partial<Express.Multer.File>) => void
): void;
/**
* Remove the file described by `file`, then invoke the callback with.
*
* `file` contains all the properties available to `_handleFile`, as
* well as those returned by `_handleFile`.
*
* @param req The Express `Request` object.
* @param file Object containing information about the processed file.
* @param callback Callback to indicate completion.
*/
_removeFile(
req: Request,
file: Express.Multer.File,
callback: (error: Error | null) => void
): void;
}
interface DiskStorageOptions {
/**
* A string or function that determines the destination path for uploaded
* files. If a string is passed and the directory does not exist, Multer
* attempts to create it recursively. If neither a string or a function
* is passed, the destination defaults to `os.tmpdir()`.
*
* @param req The Express `Request` object.
* @param file Object containing information about the processed file.
* @param callback Callback to determine the destination path.
*/
destination?: string | ((
req: Request,
file: Express.Multer.File,
callback: (error: Error | null, destination: string) => void
) => void) | undefined;
/**
* A function that determines the name of the uploaded file. If nothing
* is passed, Multer will generate a 32 character pseudorandom hex string
* with no extension.
*
* @param req The Express `Request` object.
* @param file Object containing information about the processed file.
* @param callback Callback to determine the name of the uploaded file.
*/
filename?(
req: Request,
file: Express.Multer.File,
callback: (error: Error | null, filename: string) => void
): void;
}
/**
* An object describing a field name and the maximum number of files with
* that field name to accept.
*/
interface Field {
/** The field name. */
name: string;
/** Optional maximum number of files per field to accept. (Default: Infinity) */
maxCount?: number | undefined;
}
}
export = multer;

View File

@@ -0,0 +1,62 @@
{
"name": "@types/multer",
"version": "1.4.7",
"description": "TypeScript definitions for multer",
"homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/multer",
"license": "MIT",
"contributors": [
{
"name": "jt000",
"url": "https://github.com/jt000",
"githubUsername": "jt000"
},
{
"name": "vilicvane",
"url": "https://github.com/vilic",
"githubUsername": "vilic"
},
{
"name": "David Broder-Rodgers",
"url": "https://github.com/DavidBR-SW",
"githubUsername": "DavidBR-SW"
},
{
"name": "Michael Ledin",
"url": "https://github.com/mxl",
"githubUsername": "mxl"
},
{
"name": "HyunSeob Lee",
"url": "https://github.com/hyunseob",
"githubUsername": "hyunseob"
},
{
"name": "Pierre Tchuente",
"url": "https://github.com/PierreTchuente",
"githubUsername": "PierreTchuente"
},
{
"name": "Oliver Emery",
"url": "https://github.com/thrymgjol",
"githubUsername": "thrymgjol"
},
{
"name": "Piotr Błażejewicz",
"url": "https://github.com/peterblazejewicz",
"githubUsername": "peterblazejewicz"
}
],
"main": "",
"types": "index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git",
"directory": "types/multer"
},
"scripts": {},
"dependencies": {
"@types/express": "*"
},
"typesPublisherContentHash": "32b2d0cc5d84f278945858a1be2b00bcdd046da7c6e0886d5afd0d1e002ee95a",
"typeScriptVersion": "3.6"
}

View File

@@ -37,7 +37,8 @@
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"typeorm": "^0.3.10"
"typeorm": "^0.3.10",
"uuid": "^9.0.0"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
@@ -45,6 +46,7 @@
"@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.8",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/passport-jwt": "^3.0.7",
"@types/passport-local": "^1.0.34",
@@ -2268,6 +2270,15 @@
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA=="
},
"node_modules/@types/multer": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz",
"integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==",
"dev": true,
"dependencies": {
"@types/express": "*"
}
},
"node_modules/@types/node": {
"version": "16.11.68",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.68.tgz",
@@ -11422,6 +11433,15 @@
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA=="
},
"@types/multer": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz",
"integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==",
"dev": true,
"requires": {
"@types/express": "*"
}
},
"@types/node": {
"version": "16.11.68",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.68.tgz",

View File

@@ -49,7 +49,8 @@
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"typeorm": "^0.3.10"
"typeorm": "^0.3.10",
"uuid": "^9.0.0"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
@@ -57,6 +58,7 @@
"@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.8",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/passport-jwt": "^3.0.7",
"@types/passport-local": "^1.0.34",

View File

@@ -46,6 +46,7 @@ export class AuthenticationController {
@UseGuards(AuthenticateGuard)
logout(@Req() request, @Res() response, @Next() next) {
this.userService.setIsTwoFactorAuthenticatedWhenLogout(request.user.id);
this.userService.updateStatus(request.user.id, 'disconnected');
request.logout(function(err) {
if (err) { return next(err); }
response.redirect('/');

View File

@@ -24,7 +24,7 @@ export class AuthenticateGuard implements CanActivate {
export class TwoFactorGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
console.log("TwoFactorGuard : Is User authenticated : " + request.isAuthenticated() + " and has 2FA enabled : " + request.user.isEnabledTwoFactorAuth + " and has 2FA verified : " + request.user.isTwoFactorAuthenticated);
//console.log("TwoFactorGuard : Is User authenticated : " + request.isAuthenticated() + " and has 2FA enabled : " + request.user.isEnabledTwoFactorAuth + " and has 2FA verified : " + request.user.isTwoFactorAuthenticated);
return request.isAuthenticated() && (request.user.isEnabledTwoFactorAuth === false
|| request.user.isTwoFactorAuthenticated === true);
}

View File

@@ -5,23 +5,23 @@ import { AuthenticationService } from "../authentication.service";
import { CreateUsersDto } from "src/users/dto/create-users.dto";
@Injectable()
export class FortyTwoStrategy extends PassportStrategy(Strategy, "42") {
constructor(private authenticationService: AuthenticationService) {
super({
clientID: process.env.FORTYTWO_CLIENT_ID,
clientSecret: process.env.FORTYTWO_CLIENT_SECRET,
callbackURL: process.env.FORTYTWO_CALLBACK_URL,
scope: ["public"],
});
}
export class FortyTwoStrategy extends PassportStrategy(Strategy, "42") {
constructor(private authenticationService: AuthenticationService) {
super({
clientID: process.env.FORTYTWO_CLIENT_ID,
clientSecret: process.env.FORTYTWO_CLIENT_SECRET,
callbackURL: process.env.FORTYTWO_CALLBACK_URL,
scope: ["public"],
});
}
async validate(accessToken: string, refreshToken: string, profile: Profile, callbackURL: string) {
console.log("Validate inside strategy.ts");
console.log(profile.id, profile.username, profile.phoneNumbers[0].value, profile.emails[0].value, profile.photos[0].value);
const userDTO: CreateUsersDto = { fortyTwoId: profile.id, username: profile.username, email: profile.emails[0].value, image_url: profile.photos[0].value, isEnabledTwoFactorAuth: false };
const user = await this.authenticationService.validateUser(userDTO);
if (!user)
throw new UnauthorizedException();
return user;
}
async validate(accessToken: string, refreshToken: string, profile: Profile, callbackURL: string) {
console.log("Validate inside strategy.ts");
console.log(profile.id, profile.username, profile.phoneNumbers[0].value, profile.emails[0].value, profile.photos[0].value);
const userDTO: CreateUsersDto = { fortyTwoId: profile.id, username: profile.username, email: profile.emails[0].value, image_url: 'default.png', isEnabledTwoFactorAuth: false , status: "connected" };
const user = await this.authenticationService.validateUser(userDTO);
if (!user)
throw new UnauthorizedException();
return user;
}
}

View File

@@ -0,0 +1,15 @@
import { randomUUID } from "crypto";
import { diskStorage } from "multer";
import path from "path";
export const storageForAvatar = {
storage: diskStorage({
destination: './uploads/avatars',
filename: (req, file, cb) => {
console.log(file);
const filename : string = file.originalname.split(' ').join('_') + randomUUID();
const extension : string = path.extname(file.originalname);
cb(null, `${filename}${extension}`);
}
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -9,6 +9,8 @@ export class CreateUsersDto {
readonly email: string;
@IsString()
readonly image_url: string;
@IsString()
readonly status: string;
@IsBoolean()
readonly isEnabledTwoFactorAuth: boolean;
}

View File

@@ -5,4 +5,4 @@
import { OmitType, PartialType } from "@nestjs/mapped-types";
import { CreateUsersDto } from "./create-users.dto";
export class UpdateUsersDto extends OmitType(CreateUsersDto, ['fortyTwoId', 'email'] as const){}
export class UpdateUsersDto extends OmitType(CreateUsersDto, ['fortyTwoId', 'email', 'image_url', 'status'] as const){}

View File

@@ -21,14 +21,14 @@ export class User {
@IsEmail()
email: string;
@Column({ nullable: true })
@Column()
image_url : string;
@Column({ nullable: true })
phone: string;
@Column('json', { nullable: true })
status: [string];
@Column({ default: 'disconnected' })
status: string;
// @Column()
// isFirstConnection: boolean;

View File

@@ -1,15 +1,29 @@
import {
Body, Controller, Delete, Get, HttpCode,
HttpStatus, Param, Patch, Post, Query, Req, UseGuards
Body, Controller, Delete, Get, HttpException, NotFoundException, Patch, Post, Query, Req, Res, UploadedFile, UseGuards, UseInterceptors
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { AuthenticateGuard, TwoFactorGuard } from 'src/auth/42/guards/42guards';
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
import { ValidationPipe } from 'src/common/validation/validation.pipe';
import { CreateUsersDto } from './dto/create-users.dto';
import { UpdateUsersDto } from './dto/update-users.dto';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';
import { diskStorage } from 'multer';
import path from 'path';
import { randomUUID } from 'crypto';
import { of } from 'rxjs';
export const storageForAvatar = {
storage: diskStorage({
destination: './uploads/avatars',
filename: (req, file, cb) => {
const filename : string = file.originalname.split(' ').join('_') + randomUUID();
console.log("RES : " )
const extension : string = path.extname(file.originalname);
cb(null, `${filename}${extension}`);
}
})
}
@Controller('user')
export class UsersController {
@@ -61,4 +75,28 @@ export class UsersController {
remove(@Req() req) {
return this.usersService.remove(req.user.id);
}
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
@Post('avatar')
@UseInterceptors(FileInterceptor('file', storageForAvatar))
uploadAvatar(@UploadedFile() file, @Req() request){
const user : User = request.user;
this.usersService.updateAvatar(user.id, file.filename);
}
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
@Get('avatar')
getAvatar(@Req() request, @Res() response) {
const promise = this.usersService.getAvatarUrl(request.user.id).then((url) =>
{
if (url)
return of(response.sendFile(process.cwd() + '/uploads/avatars/' + url));
else
throw new NotFoundException('Avatar not found');
});
return promise;
}
}

View File

@@ -1,6 +1,6 @@
import { HttpCode, HttpException, HttpStatus, Injectable, NotFoundException, } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { NotFoundError } from 'rxjs';
import { NotFoundError, of } from 'rxjs';
import { User } from './entities/user.entity';
import { ConnectionOptionsReader, Repository } from 'typeorm';
import { CreateUsersDto } from './dto/create-users.dto';
@@ -9,6 +9,7 @@ import { Friendship } from '../friendship/entities/friendship.entity';
import { isNumberString } from 'class-validator';
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
import { PartialUsersDto } from './dto/partial-users.dto';
import { join } from 'path';
// On va devoir sûrement trouver un moyen plus simple pour passer l'id, sûrement via des pipes
// ou des interceptors, mais pour l'instant on va faire comme ça.
@@ -65,7 +66,7 @@ export class UsersService {
}
async update(id: string, updateUserDto: UpdateUsersDto) {
console.log(`Update user ${id} with ${updateUserDto.image_url} + ${updateUserDto.isEnabledTwoFactorAuth}`);
console.log(`Update user ${id} with ${updateUserDto.isEnabledTwoFactorAuth}`);
const user = await this.userRepository.preload(
{id: +id,
...updateUserDto});
@@ -92,4 +93,22 @@ export class UsersService {
async setAuthenticatorSecret(id: number, secret: string) {
return this.userRepository.update(id, {secretTwoFactorAuth: secret});
}
async updateStatus(id: number, status: string) {
return this.userRepository.update(id, {status: status});
}
async updateAvatar(id: number, avatar: string) {
return this.userRepository.update(id, {image_url: avatar});
}
async getAvatarUrl(id: number) {
const user = await this.userRepository.findOneBy({id: id});
if (!user)
throw new HttpException(`The user could not be found.`,HttpStatus.NOT_FOUND);
if (!user.image_url)
throw new HttpException(`The user has no avatar.`,HttpStatus.NOT_FOUND);
console.log(user.image_url);
return user.image_url;
}
}

View File

@@ -1,3 +0,0 @@
FROM nginx:alpine
COPY ./conf/default.conf /etc/nginx/conf.d/default.conf

View File

@@ -1,9 +1,10 @@
<script>
import { onMount } from "svelte";
import { onMount } from "svelte";
let avatarUser, postVar, newAvatar, fileinput;
let usernameSv = "";
let image_urlSv = "";
let uploadAvatarSuccess = false;
let gAuth = false;
let errors = {usernameSv, image_urlSv};
onMount(async () => {
@@ -11,11 +12,29 @@
then(response => response.json()).
then(data => {
usernameSv = data.username;
image_urlSv = data.image_url;
gAuth = data.isEnabledTwoFactorAuth;
});
await fetch("http://transcendance:8080/api/v2/user/avatar", {method: "GET"}).
then(response => {return response.blob()}).
then(data => {
const url = URL.createObjectURL(data);
avatarUser = url;
});
});
$: uploadAvatar = async() => {
const data = new FormData();
data.append("file", newAvatar[0]);
console.log(data);
await fetch("http://transcendance:8080/api/v2/user/avatar",
{
method : 'POST',
body : data,
})
.then(uploadAvatarSuccess = true)
.catch(errors.image_urlSv = "Something went wrong." )
}
$: submit = async() => {
errors.usernameSv = "";
errors.image_urlSv ="";
@@ -23,10 +42,6 @@
errors.usernameSv = "Username is required";
return;
}
if (image_urlSv === undefined || image_urlSv.trim() === "") {
errors.image_urlSv = "image_url is required";
return;
}
console.log(usernameSv);
await fetch("http://transcendance:8080/api/v2/user/",
{
@@ -36,46 +51,59 @@
},
body: JSON.stringify({
"username" : usernameSv,
"image_url" : image_urlSv,
"isEnabledTwoFactorAuth" : gAuth
})
})
};
}
</script>
<body>
<main class="form-signin w-100 m-auto">
<div class="p-20">
{#if errors.image_urlSv}
<div class="alert alert-danger" role="alert">
{errors.image_urlSv}
</div>
{/if}
{#if errors.username}
<div class="alert alert-danger" role="alert">
{errors.username}
</div>
{/if}
{#if errors.image_url}
{#if uploadAvatarSuccess}
<div class="alert alert-danger" role="alert">
{errors.image_url}
You avatar has been successfully uploaded !
</div>
{/if}
<form on:submit|preventDefault={uploadAvatar}>
{#if avatarUser}
<img class="avatar" src={avatarUser} alt="" />
{/if}
<input
type="text"
bind:value={postVar}
placeholder={"choose your file"}
/>
<br />
<input
type="file"
bind:files={newAvatar} />
<br />
<button type="submit" class="p-2 bg-blue-500 text-white mt-4 px-6">
Choose avatar
</button>
</form>
<form on:submit|preventDefault={submit}>
<label for="username" class="block text-sm text-gray-600">Username</label>
<input id="username" type="text" placeholder=${usernameSv} class="block px-1 py-2 mt-2 border-2 border-gray-100 text-gray-800" bind:value={usernameSv} />
<label for="image_url" class="block text-sm text-gray-600 mt-4">image_url</label>
<input id="image_url" type="image_url" bind:value={image_urlSv} placeholder=${image_urlSv} class="block px-1 py-2 mt-2 border-2 border-gray-100 text-gray-800" />
<div>
<input type="checkbox" bind:checked={gAuth} id="gAuth" name="gAuth">
<label for="gAuth">Enable google authenticator</label>
</div>
<button type="submit" class="p-2 bg-blue-500 text-white mt-4 px-6">
Change
</button>
</form>
</div>
</main>
</body>
<style>
body {