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. 3
      back/volume/src/api/api.module.ts
  7. 4
      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. 95
      back/volume/src/chat/chat.gateway.ts
  11. 22
      back/volume/src/chat/chat.module.ts
  12. 54
      back/volume/src/chat/chat.service.ts
  13. 32
      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. 3
      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. 43
      back/volume/src/users/user.entity.ts
  21. 66
      back/volume/src/users/users.controller.ts
  22. 32
      back/volume/src/users/users.service.ts

1
.gitignore

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

2
back/volume/.gitignore

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

130
back/volume/package-lock.json

@ -16,8 +16,10 @@
"@nestjs/mapped-types": "^1.2.2", "@nestjs/mapped-types": "^1.2.2",
"@nestjs/passport": "^9.0.3", "@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.0.0", "@nestjs/platform-express": "^9.0.0",
"@nestjs/platform-socket.io": "^9.3.9",
"@nestjs/platform-ws": "^9.2.0", "@nestjs/platform-ws": "^9.2.0",
"@nestjs/schematics": "^9.0.0", "@nestjs/schematics": "^9.0.0",
"@nestjs/swagger": "^6.2.1",
"@nestjs/testing": "^9.0.0", "@nestjs/testing": "^9.0.0",
"@nestjs/typeorm": "^9.0.1", "@nestjs/typeorm": "^9.0.1",
"@nestjs/websockets": "^9.2.0", "@nestjs/websockets": "^9.2.0",
@ -25,6 +27,7 @@
"@types/cookie-parser": "^1.4.3", "@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/express-session": "^1.17.6", "@types/express-session": "^1.17.6",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0", "@types/node": "^16.0.0",
"@types/passport": "^1.0.12", "@types/passport": "^1.0.12",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
@ -1731,6 +1734,40 @@
"@nestjs/core": "^9.0.0" "@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": { "node_modules/@nestjs/platform-ws": {
"version": "9.3.9", "version": "9.3.9",
"resolved": "https://registry.npmjs.org/@nestjs/platform-ws/-/platform-ws-9.3.9.tgz", "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", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "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": { "node_modules/@nestjs/testing": {
"version": "9.3.9", "version": "9.3.9",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-9.3.9.tgz", "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", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" "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": { "node_modules/@types/node": {
"version": "16.18.3", "version": "16.18.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.3.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.3.tgz",
@ -9641,6 +9717,11 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/symbol-observable": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
@ -12147,6 +12228,30 @@
"tslib": "2.5.0" "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": { "@nestjs/platform-ws": {
"version": "9.3.9", "version": "9.3.9",
"resolved": "https://registry.npmjs.org/@nestjs/platform-ws/-/platform-ws-9.3.9.tgz", "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": { "@nestjs/testing": {
"version": "9.3.9", "version": "9.3.9",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-9.3.9.tgz", "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", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" "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": { "@types/node": {
"version": "16.18.3", "version": "16.18.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.3.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.3.tgz",
@ -18033,6 +18158,11 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true "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": { "symbol-observable": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", "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/mapped-types": "^1.2.2",
"@nestjs/passport": "^9.0.3", "@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.0.0", "@nestjs/platform-express": "^9.0.0",
"@nestjs/platform-socket.io": "^9.3.9",
"@nestjs/platform-ws": "^9.2.0", "@nestjs/platform-ws": "^9.2.0",
"@nestjs/schematics": "^9.0.0", "@nestjs/schematics": "^9.0.0",
"@nestjs/swagger": "^6.2.1",
"@nestjs/testing": "^9.0.0", "@nestjs/testing": "^9.0.0",
"@nestjs/typeorm": "^9.0.1", "@nestjs/typeorm": "^9.0.1",
"@nestjs/websockets": "^9.2.0", "@nestjs/websockets": "^9.2.0",
@ -37,6 +39,7 @@
"@types/cookie-parser": "^1.4.3", "@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/express-session": "^1.17.6", "@types/express-session": "^1.17.6",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0", "@types/node": "^16.0.0",
"@types/passport": "^1.0.12", "@types/passport": "^1.0.12",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",

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

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

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

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

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

@ -28,7 +28,7 @@ import { UsersModule } from './users/users.module'
ChatModule, ChatModule,
DbModule, DbModule,
PongModule, 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 { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config' import { ConfigService } from '@nestjs/config'
import { PassportStrategy } from '@nestjs/passport' 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 { UsersService } from 'src/users/users.service'
import { User } from 'src/users/user.entity' import { User } from 'src/users/user.entity'
import { get } from 'https'
import { createWriteStream } from 'fs'
@Injectable() @Injectable()
export class FtStrategy extends PassportStrategy(Strategy, '42') { export class FtStrategy extends PassportStrategy(Strategy, '42') {
@ -34,8 +36,12 @@ export class FtStrategy extends PassportStrategy(Strategy, '42') {
const newUser = new User() const newUser = new User()
newUser.id_42 = profile.id as number newUser.id_42 = profile.id as number
newUser.username = profile.displayName as string newUser.username = profile.displayName as string
newUser.avatar = profile._json.image.versions.small as string newUser.avatar = id_42 + '.jpg'
this.usersService.create(newUser) 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) 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

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

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

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

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

32
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 { import {
BeforeInsert, BeforeInsert,
Column, Column,
@ -6,44 +6,46 @@ import {
JoinTable, JoinTable,
ManyToMany, ManyToMany,
OneToMany, OneToMany,
PrimaryGeneratedColumn, PrimaryGeneratedColumn
} from 'typeorm'; } from 'typeorm'
import { Message } from './message.entity'; import { Message } from './message.entity'
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt'
@Entity() @Entity()
export class Channel { export class Channel {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number
@Column() @Column()
name: string; name: string
@ManyToMany(() => User) @ManyToMany(() => User)
@JoinTable() @JoinTable()
owners: User[]; owners: User[]
@ManyToMany(() => User) @ManyToMany(() => User)
@JoinTable() @JoinTable()
users: User[]; users: User[]
@OneToMany(() => Message, (message: Message) => message.channel) @OneToMany(() => Message, (message: Message) => message.channel)
messages: Message[]; messages: Message[]
@OneToMany(() => User, (user: User) => user.id) // refuse connection @OneToMany(() => User, (user: User) => user.id) // refuse connection
banned: User[]; banned: User[]
@OneToMany(() => User, (user: User) => user.id) // refuse post @OneToMany(() => User, (user: User) => user.id) // refuse post
muted: User[]; muted: User[]
@Column({ select: false }) @Column({ select: false })
password: string; password: string
@BeforeInsert() @BeforeInsert()
async hashPassword () { async hashPassword () {
this.password = await bcrypt.hash( this.password = await bcrypt.hash(
this.password, 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 { export class CreateChannelDto {
@IsString() @IsString()
@IsAlpha() @IsAlpha()
name: string; name: string
@IsPositive() @IsPositive()
owner: number; owner: number
@IsOptional() @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 { import {
Column, Column,
CreateDateColumn, CreateDateColumn,
@ -6,26 +6,28 @@ import {
JoinColumn, JoinColumn,
JoinTable, JoinTable,
ManyToOne, ManyToOne,
PrimaryGeneratedColumn, PrimaryGeneratedColumn
} from 'typeorm'; } from 'typeorm'
import { Channel } from './channel.entity'; import { Channel } from './channel.entity'
@Entity() @Entity()
export class Message { export class Message {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number
@Column() @Column()
text: string; text: string
@ManyToOne(() => User, (author: User) => author.messages) @ManyToOne(() => User, (author: User) => author.messages)
@JoinColumn() @JoinColumn()
author: User; author: User
@ManyToOne(() => Channel, (channel: Channel) => channel.messages) @ManyToOne(() => Channel, (channel: Channel) => channel.messages)
@JoinTable() @JoinTable()
channel: Channel; channel: Channel
@CreateDateColumn() @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 { PartialType } from '@nestjs/mapped-types'
import { CreateChannelDto } from './create-channel.dto'; import { CreateChannelDto } from './create-channel.dto'
import { Message } from './message.entity'; import { type Message } from './message.entity'
import { User } from 'src/users/user.entity'; import { type User } from 'src/users/user.entity'
import { IsString } from 'class-validator'; import { IsString } from 'class-validator'
export class UpdateChannelDto extends PartialType(CreateChannelDto) { 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() @IsString()
password: string; password: string
} }

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

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

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

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

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

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

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

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

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

@ -4,11 +4,31 @@ import {
Post, Post,
Body, Body,
Param, Param,
ParseIntPipe ParseIntPipe,
UploadedFile,
UseGuards,
UseInterceptors,
Req,
Res,
StreamableFile,
BadRequestException
} from '@nestjs/common' } from '@nestjs/common'
import { FileInterceptor } from '@nestjs/platform-express'
import { diskStorage } from 'multer'
import { type User } from './user.entity' import { type User } from './user.entity'
import { UsersService } from './users.service' 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') @Controller('users')
export class UsersController { export class UsersController {
constructor (private readonly usersService: UsersService) {} constructor (private readonly usersService: UsersService) {}
@ -27,4 +47,46 @@ export class UsersController {
update (@Param('id', ParseIntPipe) id: number, @Body() user: UpdateUserDto) { update (@Param('id', ParseIntPipe) id: number, @Body() user: UpdateUserDto) {
this.usersService.update(id, user) 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)
}
} }

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

@ -1,11 +1,9 @@
import { import { Injectable, NotFoundException } from '@nestjs/common'
Injectable,
NotFoundException
} from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm' import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm' import { Repository } from 'typeorm'
import { User } from './user.entity' import { User } from './user.entity'
import { type CreateUserDto, type UpdateUserDto } from './user.dto' import { type CreateUserDto, type UpdateUserDto } from './user.dto'
import { type Channel } from 'src/chat/model/channel.entity'
@Injectable() @Injectable()
export class UsersService { export class UsersService {
@ -18,15 +16,11 @@ export class UsersService {
} }
async getOneUser (username: string): Promise<User | null> { async getOneUser (username: string): Promise<User | null> {
const user = await this.usersRepository.findOneBy({ username: username }) return await this.usersRepository.findOneBy({ username })
if (user) return user
throw new NotFoundException(`User with username: ${username} not found`)
} }
async getOneUser42 (id_42: number): Promise<User | null> { async getOneUser42 (id_42: number): Promise<User | null> {
const user = await this.usersRepository.findOneBy({ id_42: id_42 }) return await this.usersRepository.findOneBy({ id_42 })
if (user) return user;
throw new NotFoundException(`User with id_42: ${id_42} not found`)
} }
async create (userData: CreateUserDto) { async create (userData: CreateUserDto) {
@ -39,14 +33,28 @@ export class UsersService {
} }
async findOne (id: number) { async findOne (id: number) {
const user = await this.usersRepository.findOneBy({ id: id }) const user = await this.usersRepository.findOneBy({ id })
if (user) return user; if (user) return user
throw new NotFoundException(`User #${id} not found`) throw new NotFoundException(`User #${id} not found`)
} }
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) { async update (id: number, changes: UpdateUserDto) {
const updatedUser = await this.findOne(id) const updatedUser = await this.findOne(id)
this.usersRepository.merge(updatedUser, changes) this.usersRepository.merge(updatedUser, changes)
return await this.usersRepository.save(updatedUser) return await this.usersRepository.save(updatedUser)
} }
async addAvatar (userId: number, filename: string) {
await this.usersRepository.update(userId, {
avatar: filename
})
}
} }

Loading…
Cancel
Save