Browse Source

avatars

master
nicolas-arnaud 2 years ago
parent
commit
ac08a5c4f4
  1. 1
      .gitignore
  2. 2
      back/volume/.gitignore
  3. 130
      back/volume/package-lock.json
  4. 3
      back/volume/package.json
  5. 1
      back/volume/src/api/api.controller.ts
  6. 5
      back/volume/src/api/api.module.ts
  7. 6
      back/volume/src/app.module.ts
  8. 10
      back/volume/src/auth/42.strategy.ts
  9. 8
      back/volume/src/auth/requestWithUser.interface.ts
  10. 111
      back/volume/src/chat/chat.gateway.ts
  11. 22
      back/volume/src/chat/chat.module.ts
  12. 74
      back/volume/src/chat/chat.service.ts
  13. 38
      back/volume/src/chat/model/channel.entity.ts
  14. 8
      back/volume/src/chat/model/create-channel.dto.ts
  15. 20
      back/volume/src/chat/model/message.entity.ts
  16. 24
      back/volume/src/chat/model/update-channel.dto.ts
  17. 5
      back/volume/src/db/db.module.ts
  18. 6
      back/volume/src/types.d.ts
  19. 17
      back/volume/src/users/user.dto.ts
  20. 47
      back/volume/src/users/user.entity.ts
  21. 66
      back/volume/src/users/users.controller.ts
  22. 50
      back/volume/src/users/users.service.ts

1
.gitignore

@ -1,2 +1 @@
.env
*/volume/.env

2
back/volume/.gitignore

@ -1,3 +1,5 @@
avatars/
.env
# Logs
logs

130
back/volume/package-lock.json

@ -16,8 +16,10 @@
"@nestjs/mapped-types": "^1.2.2",
"@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/platform-socket.io": "^9.3.9",
"@nestjs/platform-ws": "^9.2.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/swagger": "^6.2.1",
"@nestjs/testing": "^9.0.0",
"@nestjs/typeorm": "^9.0.1",
"@nestjs/websockets": "^9.2.0",
@ -25,6 +27,7 @@
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.13",
"@types/express-session": "^1.17.6",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/passport": "^1.0.12",
"@types/ws": "^8.5.3",
@ -1731,6 +1734,40 @@
"@nestjs/core": "^9.0.0"
}
},
"node_modules/@nestjs/platform-socket.io": {
"version": "9.3.9",
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-9.3.9.tgz",
"integrity": "sha512-fWlET24udsVjIolSjrIIj8vGqixnTXrQnrEKF1nqFpE8BW9O8Eji00Ih/A2z0MU/8fTHEiokyBIDAX5IKvhKzQ==",
"dependencies": {
"socket.io": "4.6.0",
"tslib": "2.5.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/nest"
},
"peerDependencies": {
"@nestjs/common": "^9.0.0",
"@nestjs/websockets": "^9.0.0",
"rxjs": "^7.1.0"
}
},
"node_modules/@nestjs/platform-socket.io/node_modules/socket.io": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.0.tgz",
"integrity": "sha512-b65bp6INPk/BMMrIgVvX12x3Q+NqlGqSlTuvKQWt0BUJ3Hyy3JangBl7fEoWZTXbOKlCqNPbQ6MbWgok/km28w==",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.2",
"engine.io": "~6.4.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/@nestjs/platform-ws": {
"version": "9.3.9",
"resolved": "https://registry.npmjs.org/@nestjs/platform-ws/-/platform-ws-9.3.9.tgz",
@ -1837,6 +1874,37 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@nestjs/swagger": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-6.2.1.tgz",
"integrity": "sha512-9M2vkfJHIzLqDZwvM5TEZO0MxRCvIb0xVy0LsmWwxH1lrb0z/4MhU+r2CWDhBtTccVJrKxVPiU2s3T3b9uUJbg==",
"dependencies": {
"@nestjs/mapped-types": "1.2.2",
"js-yaml": "4.1.0",
"lodash": "4.17.21",
"path-to-regexp": "3.2.0",
"swagger-ui-dist": "4.15.5"
},
"peerDependencies": {
"@fastify/static": "^6.0.0",
"@nestjs/common": "^9.0.0",
"@nestjs/core": "^9.0.0",
"class-transformer": "*",
"class-validator": "*",
"reflect-metadata": "^0.1.12"
},
"peerDependenciesMeta": {
"@fastify/static": {
"optional": true
},
"class-transformer": {
"optional": true
},
"class-validator": {
"optional": true
}
}
},
"node_modules/@nestjs/testing": {
"version": "9.3.9",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-9.3.9.tgz",
@ -2259,6 +2327,14 @@
"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==",
"dependencies": {
"@types/express": "*"
}
},
"node_modules/@types/node": {
"version": "16.18.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.3.tgz",
@ -9641,6 +9717,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/swagger-ui-dist": {
"version": "4.15.5",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz",
"integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA=="
},
"node_modules/symbol-observable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
@ -12147,6 +12228,30 @@
"tslib": "2.5.0"
}
},
"@nestjs/platform-socket.io": {
"version": "9.3.9",
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-9.3.9.tgz",
"integrity": "sha512-fWlET24udsVjIolSjrIIj8vGqixnTXrQnrEKF1nqFpE8BW9O8Eji00Ih/A2z0MU/8fTHEiokyBIDAX5IKvhKzQ==",
"requires": {
"socket.io": "4.6.0",
"tslib": "2.5.0"
},
"dependencies": {
"socket.io": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.0.tgz",
"integrity": "sha512-b65bp6INPk/BMMrIgVvX12x3Q+NqlGqSlTuvKQWt0BUJ3Hyy3JangBl7fEoWZTXbOKlCqNPbQ6MbWgok/km28w==",
"requires": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.2",
"engine.io": "~6.4.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.1"
}
}
}
},
"@nestjs/platform-ws": {
"version": "9.3.9",
"resolved": "https://registry.npmjs.org/@nestjs/platform-ws/-/platform-ws-9.3.9.tgz",
@ -12226,6 +12331,18 @@
}
}
},
"@nestjs/swagger": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-6.2.1.tgz",
"integrity": "sha512-9M2vkfJHIzLqDZwvM5TEZO0MxRCvIb0xVy0LsmWwxH1lrb0z/4MhU+r2CWDhBtTccVJrKxVPiU2s3T3b9uUJbg==",
"requires": {
"@nestjs/mapped-types": "1.2.2",
"js-yaml": "4.1.0",
"lodash": "4.17.21",
"path-to-regexp": "3.2.0",
"swagger-ui-dist": "4.15.5"
}
},
"@nestjs/testing": {
"version": "9.3.9",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-9.3.9.tgz",
@ -12590,6 +12707,14 @@
"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==",
"requires": {
"@types/express": "*"
}
},
"@types/node": {
"version": "16.18.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.3.tgz",
@ -18033,6 +18158,11 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
"swagger-ui-dist": {
"version": "4.15.5",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz",
"integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA=="
},
"symbol-observable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",

3
back/volume/package.json

@ -28,8 +28,10 @@
"@nestjs/mapped-types": "^1.2.2",
"@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/platform-socket.io": "^9.3.9",
"@nestjs/platform-ws": "^9.2.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/swagger": "^6.2.1",
"@nestjs/testing": "^9.0.0",
"@nestjs/typeorm": "^9.0.1",
"@nestjs/websockets": "^9.2.0",
@ -37,6 +39,7 @@
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.13",
"@types/express-session": "^1.17.6",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/passport": "^1.0.12",
"@types/ws": "^8.5.3",

1
back/volume/src/api/api.controller.ts

@ -25,4 +25,3 @@ export class ApiController {
return [1, 2, 3]
}
}

5
back/volume/src/api/api.module.ts

@ -2,7 +2,6 @@ import { Module } from '@nestjs/common'
import { ApiController } from './api.controller'
@Module({
controllers: [ApiController],
controllers: [ApiController]
})
export class ApiModule { }
export class ApiModule {}

6
back/volume/src/app.module.ts

@ -28,7 +28,7 @@ import { UsersModule } from './users/users.module'
ChatModule,
DbModule,
PongModule,
UsersModule,
],
UsersModule
]
})
export class AppModule { }
export class AppModule {}

10
back/volume/src/auth/42.strategy.ts

@ -1,9 +1,11 @@
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { PassportStrategy } from '@nestjs/passport'
import { Strategy, Profile, VerifyCallback } from 'passport-42'
import { Strategy, type Profile, type VerifyCallback } from 'passport-42'
import { UsersService } from 'src/users/users.service'
import { User } from 'src/users/user.entity'
import { get } from 'https'
import { createWriteStream } from 'fs'
@Injectable()
export class FtStrategy extends PassportStrategy(Strategy, '42') {
@ -34,8 +36,12 @@ export class FtStrategy extends PassportStrategy(Strategy, '42') {
const newUser = new User()
newUser.id_42 = profile.id as number
newUser.username = profile.displayName as string
newUser.avatar = profile._json.image.versions.small as string
newUser.avatar = id_42 + '.jpg'
this.usersService.create(newUser)
const file = createWriteStream('avatars/' + id_42 + '.jpg')
get(profile._json.image.versions.small, function (response) {
response.pipe(file)
})
}
return cb(null, profile)
}

8
back/volume/src/auth/requestWithUser.interface.ts

@ -0,0 +1,8 @@
import { type Request } from 'express'
import type User from 'src/users/user.entity'
interface RequestWithUser extends Request {
user: User
}
export default RequestWithUser

111
back/volume/src/chat/chat.gateway.ts

@ -1,17 +1,18 @@
import {
OnGatewayConnection,
OnGatewayDisconnect,
type OnGatewayConnection,
type OnGatewayDisconnect,
MessageBody,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { Socket, Server } from 'socket.io';
import { User } from 'src/users/user.entity';
import { UsersService } from 'src/users/users.service';
import { UnauthorizedException } from '@nestjs/common';
import { ChatService } from './chat.service';
import { Channel } from './model/channel.entity';
import { Message } from './model/message.entity';
WebSocketServer
} from '@nestjs/websockets'
import { Socket, Server } from 'socket.io'
import { type User } from 'src/users/user.entity'
import { UsersService } from 'src/users/users.service'
import { UnauthorizedException } from '@nestjs/common'
import { ChatService } from './chat.service'
import { Channel } from './model/channel.entity'
import { Message } from './model/message.entity'
import { CreateChannelDto } from './model/create-channel.dto'
@ -20,71 +21,81 @@ import { CreateChannelDto } from './model/create-channel.dto'
origin: [
'http://localhost:5000',
'http://localhost:80',
'http://localhost:8080',
],
},
'http://localhost:8080'
]
}
})
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;
server: Server
constructor(
private userService: UsersService,
private chatservice: ChatService,
) { }
constructor (
private readonly userService: UsersService,
private readonly chatService: ChatService
) {}
async handleConnection(socket: Socket) {
async handleConnection (socket: Socket) {
try {
const user: User = await this.userService.findOne(socket.data.user.id);
const user: User = await this.userService.findOne(socket.data.user.id)
if (!user) {
socket.emit('Error', new UnauthorizedException());
socket.disconnect();
return;
socket.emit('Error', new UnauthorizedException())
// socket.disconnect();
return
} else {
socket.data.user = user;
const channels = await this.chatservice.getChannelsForUser(user.id);
socket.data.user = user
const channels = await this.chatService.getChannelsForUser(user.id)
// Only emit rooms to the specific connected client
return this.server.to(socket.id).emit('channel', channels);
return this.server.to(socket.id).emit('channel', channels)
}
} catch {
socket.emit('Error', new UnauthorizedException());
socket.disconnect();
return;
socket.emit('Error', new UnauthorizedException())
// socket.disconnect();
}
}
handleDisconnect(socket: Socket) {
socket.disconnect();
handleDisconnect (socket: Socket) {
// socket.disconnect();
}
async onCreateChannel(socket: Socket, channel: CreateChannelDto): Promise<Channel> {
return this.chatservice.createChannel(channel, socket.data.user);
@SubscribeMessage('createChannel')
async onCreateChannel (
socket: Socket,
@MessageBody() channeldto: CreateChannelDto
): Promise<Channel> {
const channel = new Channel()
channel.name = channeldto.name
const owner = await this.userService.findOne(channeldto.owner)
channel.owners.push(owner)
channel.password = channeldto.password
/// ...///
return await this.chatService.createChannel(channel, socket.data.user)
}
@SubscribeMessage('joinChannel')
async onJoinChannel(socket: Socket, channel: Channel) {
//add user to channel
const messages = await this.chatservice.findMessagesInChannelForUser(
async onJoinChannel (socket: Socket, channel: Channel) {
// add user to channel
const messages = await this.chatService.findMessagesInChannelForUser(
channel,
socket.data.user,
);
this.server.to(socket.id).emit('messages', messages);
socket.data.user
)
this.server.to(socket.id).emit('messages', messages)
}
@SubscribeMessage('leaveChannel')
async onLeaveChannel(socket: Socket) {
await this.chatservice.deleteBySocketId(socket.id);
async onLeaveChannel (socket: Socket) {
await this.chatService.deleteBySocketId(socket.id)
}
@SubscribeMessage('addMessage')
async onAddMessage(socket: Socket, message: Message) {
const createdMessage: Message = await this.chatservice.createMessage({
async onAddMessage (socket: Socket, message: Message) {
const createdMessage: Message = await this.chatService.createMessage({
...message,
author: socket.data.user,
});
const channel = await this.chatservice.getChannel(
createdMessage.channel.id,
);
//send new Message to all joined Users currently online of the channel
author: socket.data.user
})
const channel = await this.chatService.getChannel(
createdMessage.channel.id
)
const users = await this.userService.findOnlineInChannel(channel)
/// TODO: Send message to users
}
}

22
back/volume/src/chat/chat.module.ts

@ -1,20 +1,20 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from 'src/auth/auth.module';
import { UsersModule } from 'src/users/users.module';
import { ChatGateway } from './chat.gateway';
import { ChatService } from './chat.service';
import { UsersService } from 'src/users/users.service';
import { Channel } from './model/channel.entity';
import { Message } from './model/message.entity';
import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { AuthModule } from 'src/auth/auth.module'
import { UsersModule } from 'src/users/users.module'
import { ChatGateway } from './chat.gateway'
import { ChatService } from './chat.service'
import { UsersService } from 'src/users/users.service'
import { Channel } from './model/channel.entity'
import { Message } from './model/message.entity'
@Module({
imports: [
AuthModule,
UsersModule,
TypeOrmModule.forFeature([Channel]),
TypeOrmModule.forFeature([Message]),
TypeOrmModule.forFeature([Message])
],
providers: [ChatGateway, ChatService],
providers: [ChatGateway, ChatService]
})
export class ChatModule {}

74
back/volume/src/chat/chat.service.ts

@ -1,61 +1,57 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt'
import { Channel } from 'src/chat/model/channel.entity';
import { User } from 'src/users/user.entity';
import { Message } from './model/message.entity';
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Channel } from 'src/chat/model/channel.entity'
import { type User } from 'src/users/user.entity'
import { Repository } from 'typeorm'
import { Message } from './model/message.entity'
import { CreateChannelDto } from './model/create-channel.dto'
@Injectable()
export class ChatService {
constructor(
constructor (
@InjectRepository(Channel)
private readonly ChannelRepository: Repository<Channel>,
@InjectRepository(Message)
private readonly MessageRepository: Repository<Message>,
) { }
async createChannel(channelDatas: CreateChannelDto, creator: User): Promise<Channel> {
channelDatas.password = await bcrypt.hash(channelDatas.password, 10);
const newChannel = this.ChannelRepository.create(channelDatas);
await this.addCreatorToChannel(newChannel, creator);
this.ChannelRepository.save(newChannel);
newChannel.password = undefined;
return newChannel;
private readonly MessageRepository: Repository<Message>
) {}
async createChannel (Channel: Channel, creator: User): Promise<Channel> {
const newChannel = await this.addCreatorToChannel(Channel, creator)
return await this.ChannelRepository.save(newChannel)
}
async getChannelsForUser(userId: number): Promise<Channel[]> {
return this.ChannelRepository.find({}); //where userId is in User[] of channel?
async getChannelsForUser (userId: number): Promise<Channel[]> {
return await this.ChannelRepository.find({}) // where userId is in User[] of channel?
}
async addCreatorToChannel(Channel: Channel, creator: User): Promise<Channel> {
Channel.users.push(creator);
return Channel;
async addCreatorToChannel (Channel: Channel, creator: User): Promise<Channel> {
Channel.users.push(creator)
return Channel
}
async createMessage(message: Message): Promise<Message> {
return this.MessageRepository.save(this.MessageRepository.create(message));
async createMessage (message: Message): Promise<Message> {
return await this.MessageRepository.save(
this.MessageRepository.create(message)
)
}
async deleteBySocketId(socketId: string) {
return this.ChannelRepository.delete({}); // for disconnect
async deleteBySocketId (socketId: string) {
return await this.ChannelRepository.delete({}) // for disconnect
}
async getChannel(id: number): Promise<Channel | null> {
return this.ChannelRepository.findOneBy({ id });
async getChannel (id: number): Promise<Channel | null> {
return await this.ChannelRepository.findOneBy({ id })
}
async findMessagesInChannelForUser(
async findMessagesInChannelForUser (
channel: Channel,
user: User,
): Promise<Message> {
return this.MessageRepository.findOne({
where: {
channel: { id: channel.id }
},
relations: { channel: true },
})
user: User
): Promise<Message[]> {
return await this.MessageRepository.createQueryBuilder('message')
.where('message.channel = :chan', { chan: channel })
.andWhere('message.author NOT IN (:...blocked)', {
blocked: user.blocked
})
.getMany()
}
}

38
back/volume/src/chat/model/channel.entity.ts

@ -1,4 +1,4 @@
import { User } from 'src/users/user.entity';
import { User } from 'src/users/user.entity'
import {
BeforeInsert,
Column,
@ -6,44 +6,46 @@ import {
JoinTable,
ManyToMany,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Message } from './message.entity';
import * as bcrypt from 'bcrypt';
PrimaryGeneratedColumn
} from 'typeorm'
import { Message } from './message.entity'
import * as bcrypt from 'bcrypt'
@Entity()
export class Channel {
@PrimaryGeneratedColumn()
id: number;
id: number
@Column()
name: string;
name: string
@ManyToMany(() => User)
@JoinTable()
owners: User[];
owners: User[]
@ManyToMany(() => User)
@JoinTable()
users: User[];
users: User[]
@OneToMany(() => Message, (message: Message) => message.channel)
messages: Message[];
messages: Message[]
@OneToMany(() => User, (user: User) => user.id) //refuse connection
banned: User[];
@OneToMany(() => User, (user: User) => user.id) // refuse connection
banned: User[]
@OneToMany(() => User, (user: User) => user.id) //refuse post
muted: User[];
@OneToMany(() => User, (user: User) => user.id) // refuse post
muted: User[]
@Column({ select: false })
password: string;
password: string
@BeforeInsert()
async hashPassword() {
async hashPassword () {
this.password = await bcrypt.hash(
this.password,
Number(process.env.HASH_SALT),
);
Number(process.env.HASH_SALT)
)
}
}
export default Channel

8
back/volume/src/chat/model/create-channel.dto.ts

@ -1,13 +1,13 @@
import { IsPositive, IsAlpha, IsString, IsOptional } from 'class-validator';
import { IsPositive, IsAlpha, IsString, IsOptional } from 'class-validator'
export class CreateChannelDto {
@IsString()
@IsAlpha()
name: string;
name: string
@IsPositive()
owner: number;
owner: number
@IsOptional()
password: string;
password: string
}

20
back/volume/src/chat/model/message.entity.ts

@ -1,4 +1,4 @@
import { User } from 'src/users/user.entity';
import { User } from 'src/users/user.entity'
import {
Column,
CreateDateColumn,
@ -6,26 +6,28 @@ import {
JoinColumn,
JoinTable,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Channel } from './channel.entity';
PrimaryGeneratedColumn
} from 'typeorm'
import { Channel } from './channel.entity'
@Entity()
export class Message {
@PrimaryGeneratedColumn()
id: number;
id: number
@Column()
text: string;
text: string
@ManyToOne(() => User, (author: User) => author.messages)
@JoinColumn()
author: User;
author: User
@ManyToOne(() => Channel, (channel: Channel) => channel.messages)
@JoinTable()
channel: Channel;
channel: Channel
@CreateDateColumn()
created_at: Date;
created_at: Date
}
export default Message

24
back/volume/src/chat/model/update-channel.dto.ts

@ -1,22 +1,22 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateChannelDto } from './create-channel.dto';
import { Message } from './message.entity';
import { User } from 'src/users/user.entity';
import { IsString } from 'class-validator';
import { PartialType } from '@nestjs/mapped-types'
import { CreateChannelDto } from './create-channel.dto'
import { type Message } from './message.entity'
import { type User } from 'src/users/user.entity'
import { IsString } from 'class-validator'
export class UpdateChannelDto extends PartialType(CreateChannelDto) {
id: number;
id: number
users: [User];
users: [User]
messages: [Message];
messages: [Message]
owners: [number]; //user id
owners: [number] // user id
banned: [number]; //user id
banned: [number] // user id
muted: [number]; //user id
muted: [number] // user id
@IsString()
password: string;
password: string
}

5
back/volume/src/db/db.module.ts

@ -19,8 +19,7 @@ import * as Joi from 'joi'
autoLoadEntities: true,
synchronize: true
})
}),
})
]
})
export class DbModule { }
export class DbModule {}

6
back/volume/src/types.d.ts

@ -1,5 +1,5 @@
declare module 'passport-42' {
export type Profile = any;
export type VerifyCallback = any;
export class Strategy {};
export type Profile = any
export type VerifyCallback = any
export class Strategy {}
}

17
back/volume/src/users/user.dto.ts

@ -1,5 +1,6 @@
import {
IsString,
IsNumber,
IsNotEmpty,
IsEmail,
Length,
@ -7,6 +8,9 @@ import {
IsOptional
} from 'class-validator'
import { ApiProperty } from '@nestjs/swagger'
import { Express } from 'express'
export class CreateUserDto {
@IsPositive()
@IsNotEmpty()
@ -15,10 +19,6 @@ export class CreateUserDto {
@IsString()
@IsNotEmpty()
readonly username: string
@IsString()
@IsNotEmpty()
readonly avatar: string
}
export class UpdateUserDto {
@ -30,10 +30,11 @@ export class UpdateUserDto {
@IsNotEmpty()
readonly username: string
@IsString()
@IsNotEmpty()
readonly avatar: string
@IsOptional()
readonly status: string
}
export class AvatarUploadDto {
@ApiProperty({ type: 'string', format: 'binary' })
file: Express.Multer.File
}

47
back/volume/src/users/user.entity.ts

@ -1,39 +1,44 @@
import {
Column,
Entity,
ManyToMany,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Message } from 'src/chat/model/message.entity';
import { Channel } from 'src/chat/model/channel.entity';
Column,
OneToOne,
OneToMany,
ManyToMany,
JoinColumn
} from 'typeorm'
import Message from 'src/chat/model/message.entity'
import Channel from 'src/chat/model/channel.entity'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
id_42: number;
id_42: number
@Column({ unique: true })
username: string;
@Column({ default: '' })
avatar: string;
username: string
@Column({ default: 'online' })
status: string;
status: string
@Column({ name: 'avatar' })
public avatar?: string
@OneToMany(() => Message, (message: Message) => message.author)
messages: Message[];
messages: Message[]
@ManyToMany(() => Channel, (channel: Channel) => channel.users)
rooms: Channel[];
rooms: Channel[]
@ManyToMany(() => User)
blocked: User[]
@OneToMany(() => User, (user) => user.id) //filter messages
blocked: User[];
@ManyToMany(() => User)
friends: User[]
//@Column({ default: { wr: -1, place: -1 } })
//rank: { wr: number; place: number };
// @Column({ default: { wr: -1, place: -1 } })
// rank: { wr: number; place: number };
}
export default User

66
back/volume/src/users/users.controller.ts

@ -4,11 +4,31 @@ import {
Post,
Body,
Param,
ParseIntPipe
ParseIntPipe,
UploadedFile,
UseGuards,
UseInterceptors,
Req,
Res,
StreamableFile,
BadRequestException
} from '@nestjs/common'
import { FileInterceptor } from '@nestjs/platform-express'
import { diskStorage } from 'multer'
import { type User } from './user.entity'
import { UsersService } from './users.service'
import { CreateUserDto, UpdateUserDto } from './user.dto'
import { CreateUserDto, UpdateUserDto, AvatarUploadDto } from './user.dto'
import RequestWithUser from 'src/auth/requestWithUser.interface'
import { FtOauthGuard } from 'src/auth/42-auth.guard'
import { ApiBody, ApiConsumes } from '@nestjs/swagger'
import { Response } from 'express'
import { createReadStream } from 'fs'
import { join } from 'path'
@Controller('users')
export class UsersController {
constructor (private readonly usersService: UsersService) {}
@ -27,4 +47,46 @@ export class UsersController {
update (@Param('id', ParseIntPipe) id: number, @Body() user: UpdateUserDto) {
this.usersService.update(id, user)
}
@Post(':id/avatar')
@UseInterceptors(
FileInterceptor('avatar', {
storage: diskStorage({
destination: 'avatars/'
}),
fileFilter: (request: Request, file: Express.Multer.File, callback) => {
if (!file.mimetype.includes('image')) {
callback(new BadRequestException('Provide a valid image'), false)
return
}
callback(null, true)
}
})
)
@ApiConsumes('multipart/form-data')
@ApiBody({
description: 'A new avatar for the user',
type: AvatarUploadDto
})
async addAvatar (
@Param('id', ParseIntPipe) id: number,
@UploadedFile() file: Express.Multer.File
) {
await this.usersService.addAvatar(id, file.filename)
}
@Get(':id/avatar')
async getAvatar (
@Param('id', ParseIntPipe) id: number,
@Res({ passthrough: true }) response: Response
) {
const user = await this.usersService.findOne(id)
const filename = user.avatar
const stream = createReadStream(join(process.cwd(), 'avatars/' + filename))
response.set({
'Content-Diposition': `inline; filename="${filename}"`,
'Content-Type': 'image/jpg'
})
return new StreamableFile(stream)
}
}

50
back/volume/src/users/users.service.ts

@ -1,52 +1,60 @@
import {
Injectable,
NotFoundException
} from '@nestjs/common'
import { Injectable, NotFoundException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from './user.entity'
import { type CreateUserDto, type UpdateUserDto } from './user.dto'
import { type Channel } from 'src/chat/model/channel.entity'
@Injectable()
export class UsersService {
constructor(
constructor (
@InjectRepository(User) private readonly usersRepository: Repository<User>
) { }
) {}
async getAllUsers(): Promise<User[]> {
async getAllUsers (): Promise<User[]> {
return await this.usersRepository.find({})
}
async getOneUser(username: string): Promise<User | null> {
const user = await this.usersRepository.findOneBy({ username: username })
if (user) return user
throw new NotFoundException(`User with username: ${username} not found`)
async getOneUser (username: string): Promise<User | null> {
return await this.usersRepository.findOneBy({ username })
}
async getOneUser42(id_42: number): Promise<User | null> {
const user = await this.usersRepository.findOneBy({ id_42: id_42 })
if (user) return user;
throw new NotFoundException(`User with id_42: ${id_42} not found`)
async getOneUser42 (id_42: number): Promise<User | null> {
return await this.usersRepository.findOneBy({ id_42 })
}
async create(userData: CreateUserDto) {
async create (userData: CreateUserDto) {
try {
const newUser= this.usersRepository.create(userData)
const newUser = this.usersRepository.create(userData)
return await this.usersRepository.save(newUser)
} catch (err) {
throw new Error(`Error creating ${err} user ${err.message}`)
}
}
async findOne(id: number) {
const user = await this.usersRepository.findOneBy({ id: id })
if (user) return user;
async findOne (id: number) {
const user = await this.usersRepository.findOneBy({ id })
if (user) return user
throw new NotFoundException(`User #${id} not found`)
}
async update(id: number, changes: UpdateUserDto) {
async findOnlineInChannel (channel: Channel): Promise<User[]> {
return await this.usersRepository
.createQueryBuilder('user')
.where('user.channel = :chan', { chan: channel })
.andWhere('user.status := status)', { status: 'online' })
.getMany()
}
async update (id: number, changes: UpdateUserDto) {
const updatedUser = await this.findOne(id)
this.usersRepository.merge(updatedUser, changes)
return await this.usersRepository.save(updatedUser)
}
async addAvatar (userId: number, filename: string) {
await this.usersRepository.update(userId, {
avatar: filename
})
}
}

Loading…
Cancel
Save