added basic auth, schemas and some ws event handling
This commit is contained in:
parent
c25480ded7
commit
538198840c
785
package-lock.json
generated
785
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@ -21,10 +21,21 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
|
"@nestjs/config": "^4.0.4",
|
||||||
"@nestjs/core": "^11.0.1",
|
"@nestjs/core": "^11.0.1",
|
||||||
|
"@nestjs/jwt": "^11.0.2",
|
||||||
|
"@nestjs/mapped-types": "*",
|
||||||
|
"@nestjs/mongoose": "^11.0.4",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
|
"@nestjs/platform-socket.io": "^11.1.19",
|
||||||
|
"@nestjs/swagger": "^11.3.0",
|
||||||
|
"@nestjs/websockets": "^11.1.19",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.15.1",
|
||||||
|
"mongoose": "^9.4.1",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1",
|
||||||
|
"socket.io": "^4.8.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
|
|||||||
@ -1,9 +1,32 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
|
import { UsersModule } from './users/users.module';
|
||||||
|
import { ConversationsModule } from './conversations/conversations.module';
|
||||||
|
import { MessagesModule } from './messages/messages.module';
|
||||||
|
import { ChatModule } from './chat/chat.module';
|
||||||
|
import { AuthModule } from './auth/auth.module';
|
||||||
|
import { MongooseModule } from '@nestjs/mongoose';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [
|
||||||
|
ConfigModule.forRoot({ isGlobal: true }), //access env anywhere
|
||||||
|
|
||||||
|
// connect db
|
||||||
|
MongooseModule.forRootAsync({
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: (configService: ConfigService) => ({
|
||||||
|
uri: configService.get<string>('DB_URI'),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
UsersModule,
|
||||||
|
ConversationsModule,
|
||||||
|
MessagesModule,
|
||||||
|
ChatModule,
|
||||||
|
AuthModule,
|
||||||
|
|
||||||
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
})
|
})
|
||||||
|
|||||||
38
src/auth/auth.controller.ts
Normal file
38
src/auth/auth.controller.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { UsersService } from './../users/users.service';
|
||||||
|
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { ApiOperation } from '@nestjs/swagger';
|
||||||
|
import { CreateUserDto } from 'src/users/dto/create-user.dto';
|
||||||
|
|
||||||
|
@Controller('auth')
|
||||||
|
export class AuthController {
|
||||||
|
constructor(private readonly authService: AuthService,
|
||||||
|
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post('/token')
|
||||||
|
@ApiOperation({ summary: 'sign in', description: 'Authenticates a user Returns a JWT access token.' })
|
||||||
|
create(@Body() createUserDto: CreateUserDto ) {
|
||||||
|
return this.authService.create(createUserDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Get()
|
||||||
|
// findAll() {
|
||||||
|
// return this.authService.findAll();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @Get(':id')
|
||||||
|
// findOne(@Param('id') id: string) {
|
||||||
|
// return this.authService.findOne(+id);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @Patch(':id')
|
||||||
|
// update(@Param('id') id: string, @Body() updateAuthDto: UpdateAuthDto) {
|
||||||
|
// return this.authService.update(+id, updateAuthDto);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @Delete(':id')
|
||||||
|
// remove(@Param('id') id: string) {
|
||||||
|
// return this.authService.remove(+id);
|
||||||
|
// }
|
||||||
|
}
|
||||||
23
src/auth/auth.module.ts
Normal file
23
src/auth/auth.module.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { AuthController } from './auth.controller';
|
||||||
|
import { UsersService } from 'src/users/users.service';
|
||||||
|
import { JwtModule, JwtService } from '@nestjs/jwt';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { UsersModule } from 'src/users/users.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports:[UsersModule,
|
||||||
|
JwtModule.registerAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: (config: ConfigService) => ({
|
||||||
|
secret: config.get('JWT_SECRET'),
|
||||||
|
signOptions: { expiresIn: config.get('JWT_EXPIRES_IN', '7') },
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
controllers: [AuthController],
|
||||||
|
providers: [AuthService],
|
||||||
|
})
|
||||||
|
export class AuthModule {}
|
||||||
45
src/auth/auth.service.ts
Normal file
45
src/auth/auth.service.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { CreateUserDto } from './../users/dto/create-user.dto';
|
||||||
|
import { User } from './../users/schemas/user.schema';
|
||||||
|
import { UsersService } from './../users/users.service';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { UpdateAuthDto } from './dto/update-auth.dto';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthService {
|
||||||
|
constructor(
|
||||||
|
private readonly usersService: UsersService,
|
||||||
|
private readonly jwtService: JwtService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(createUserDto: CreateUserDto) {
|
||||||
|
// check if user exist
|
||||||
|
let user=await this.usersService.findOne(createUserDto.name)
|
||||||
|
if(!user){
|
||||||
|
user = await this.usersService.create(createUserDto);
|
||||||
|
}
|
||||||
|
const token = this.jwtService.sign({
|
||||||
|
// sub: user.name.toLowerCase().replace(/\s+/g, '-'), // as name is unique
|
||||||
|
sub:user._id, // mongodb id
|
||||||
|
username: user.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { token, username: user.name };
|
||||||
|
}
|
||||||
|
|
||||||
|
findAll() {
|
||||||
|
return `This action returns all auth`;
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(id: number) {
|
||||||
|
return `This action returns a #${id} auth`;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(id: number, updateAuthDto: UpdateAuthDto) {
|
||||||
|
return `This action updates a #${id} auth`;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(id: number) {
|
||||||
|
return `This action removes a #${id} auth`;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/auth/dto/create-auth.dto.ts
Normal file
1
src/auth/dto/create-auth.dto.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export class CreateAuthDto {}
|
||||||
4
src/auth/dto/update-auth.dto.ts
Normal file
4
src/auth/dto/update-auth.dto.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateAuthDto } from './create-auth.dto';
|
||||||
|
|
||||||
|
export class UpdateAuthDto extends PartialType(CreateAuthDto) {}
|
||||||
184
src/chat/chat.gateway.ts
Normal file
184
src/chat/chat.gateway.ts
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
import {
|
||||||
|
WebSocketGateway,
|
||||||
|
SubscribeMessage,
|
||||||
|
MessageBody,
|
||||||
|
OnGatewayInit,
|
||||||
|
OnGatewayConnection,
|
||||||
|
OnGatewayDisconnect,
|
||||||
|
WebSocketServer,
|
||||||
|
WsException,
|
||||||
|
ConnectedSocket,
|
||||||
|
} from '@nestjs/websockets';
|
||||||
|
import { ChatService } from './chat.service';
|
||||||
|
import { Server, Socket } from 'socket.io';
|
||||||
|
import { Logger, UseGuards } from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { JoinLeaveRoomDto } from './dto/join-leave-room.dto';
|
||||||
|
import { WsJwtGuard } from './guards/ws-jwt.guard';
|
||||||
|
import { RoomMessageDto } from './dto/room-message.dto';
|
||||||
|
|
||||||
|
@WebSocketGateway({
|
||||||
|
namespace: '/chat',
|
||||||
|
cors: { origin: '*' },
|
||||||
|
})
|
||||||
|
export class ChatGateway
|
||||||
|
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly chatService: ChatService,
|
||||||
|
private readonly jwtService: JwtService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@WebSocketServer()
|
||||||
|
server!: Server;
|
||||||
|
|
||||||
|
private readonly logger = new Logger(ChatGateway.name);
|
||||||
|
|
||||||
|
private users = new Map<string, { userId: string; username: string }>(); // {socketId:{userId:'12rt3',username:'suman'}}
|
||||||
|
|
||||||
|
afterInit(server: Server) {
|
||||||
|
this.logger.log('/chat namespace initialized');
|
||||||
|
// server.setMaxListeners(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConnection(client: Socket) {
|
||||||
|
try {
|
||||||
|
// st-1: Extract token
|
||||||
|
const token =
|
||||||
|
client.handshake.auth?.token || client.handshake.headers?.authorization; // fallback
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
throw new WsException('No token provided');
|
||||||
|
}
|
||||||
|
// strip 'Bearer' prefix if present
|
||||||
|
const rawToken = token.replace(/^Bearer\s+/i, '');
|
||||||
|
|
||||||
|
// st-2: verify jwt
|
||||||
|
const payload = this.jwtService.verify(rawToken);
|
||||||
|
|
||||||
|
// st-3:attach user to client
|
||||||
|
client.data.user = {
|
||||||
|
id: payload.sub, // mongo id
|
||||||
|
username: payload.username,
|
||||||
|
};
|
||||||
|
// in memeory
|
||||||
|
this.users.set(client.id, {
|
||||||
|
userId: payload.sub,
|
||||||
|
username: payload.username,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.log(`[+] Authenticated: ${payload.username} (${client.id})`);
|
||||||
|
// tell the new client their own info
|
||||||
|
client.emit('connected', {
|
||||||
|
YourId: client.id,
|
||||||
|
username: payload.username,
|
||||||
|
message: `welcome, ${payload.username}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// tell everyone else someone joined
|
||||||
|
client.broadcast.emit('serverNotice', `[+] ${payload.username} joined`);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
// st-4: Disconnect unauthorized clients
|
||||||
|
this.logger.warn(`[!] Rejected connection: ${err}`);
|
||||||
|
client.emit('authError', {
|
||||||
|
mesage: err || 'Unauthorized',
|
||||||
|
});
|
||||||
|
|
||||||
|
client.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDisconnect(client: Socket) {
|
||||||
|
const user = this.users.get(client.id);
|
||||||
|
if (user) {
|
||||||
|
this.users.delete(client.id);
|
||||||
|
this.logger.log(`[-] Disconnected: ${user.username} (${client.id})`);
|
||||||
|
this.server.emit('serverNotice', `[-] ${user.username} left`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(WsJwtGuard)
|
||||||
|
@SubscribeMessage('joinRoom')
|
||||||
|
handleJoinRoom(
|
||||||
|
@MessageBody() body: JoinLeaveRoomDto, //roomId
|
||||||
|
@ConnectedSocket() client: Socket,
|
||||||
|
) {
|
||||||
|
|
||||||
|
// extract user
|
||||||
|
const username=client.data.user.username;
|
||||||
|
|
||||||
|
// join user in the room(front-end will provide roomId(standard appraoch))
|
||||||
|
client.join(body.roomId)
|
||||||
|
|
||||||
|
// tell the joiner he successfully joined
|
||||||
|
client.emit("roomJoined",{
|
||||||
|
roomId:body.roomId,
|
||||||
|
message:`You joined room: ${body.roomId}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// also tell others in the room except the new joiner
|
||||||
|
client.to(body.roomId).emit("roomNotice",{
|
||||||
|
roomId: body.roomId,
|
||||||
|
message: `[+] ${username} joined the room`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// return acknowledgement
|
||||||
|
return { success: true, roomId: body.roomId };
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(WsJwtGuard)
|
||||||
|
@SubscribeMessage('leaveRoom')
|
||||||
|
handleLeaveRoom(
|
||||||
|
@MessageBody() body: JoinLeaveRoomDto, //roomId
|
||||||
|
@ConnectedSocket() client: Socket,
|
||||||
|
) {
|
||||||
|
// extract user
|
||||||
|
const username=client.data.user.username;
|
||||||
|
|
||||||
|
// remove the user from the room
|
||||||
|
client.leave(body.roomId)
|
||||||
|
|
||||||
|
// confirm to the leaver
|
||||||
|
client.emit('roomLeft', { roomId: body.roomId });
|
||||||
|
|
||||||
|
// Notify remaining memebers only
|
||||||
|
client.to(body.roomId).emit('roomNotice', {
|
||||||
|
roomId: body.roomId,
|
||||||
|
message: `[-] ${username} left the room`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, roomId: body.roomId };
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(WsJwtGuard)
|
||||||
|
@SubscribeMessage('roomMessage')
|
||||||
|
handleRoomMessage(
|
||||||
|
@MessageBody() body:RoomMessageDto, //text:string
|
||||||
|
@ConnectedSocket() sender:Socket
|
||||||
|
){
|
||||||
|
// extract user
|
||||||
|
const username=sender.data.user.username
|
||||||
|
// Check if user is in room
|
||||||
|
const isMember = sender.rooms.has(body.roomId);
|
||||||
|
if (!isMember) {
|
||||||
|
sender.emit('roomError', {
|
||||||
|
message: 'You are not allowed',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`[ROOM:${body.roomId}] ${username}: ${body.text}`);
|
||||||
|
|
||||||
|
// send everone in the room(including the sender)
|
||||||
|
this.server.to(body.roomId).emit('roomMessage', {
|
||||||
|
roomId: body.roomId,
|
||||||
|
senderId: sender.id,
|
||||||
|
username,
|
||||||
|
text: body.text,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
src/chat/chat.module.ts
Normal file
20
src/chat/chat.module.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ChatService } from './chat.service';
|
||||||
|
import { ChatGateway } from './chat.gateway';
|
||||||
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
JwtModule.registerAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: (config: ConfigService) => ({
|
||||||
|
secret: config.get('JWT_SECRET'),
|
||||||
|
signOptions: { expiresIn: config.get('JWT_EXPIRES_IN', '7') },
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
providers: [ChatGateway, ChatService],
|
||||||
|
})
|
||||||
|
export class ChatModule {}
|
||||||
6
src/chat/chat.service.ts
Normal file
6
src/chat/chat.service.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ChatService {
|
||||||
|
|
||||||
|
}
|
||||||
9
src/chat/dto/chat-message.dto.ts
Normal file
9
src/chat/dto/chat-message.dto.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator';
|
||||||
|
|
||||||
|
export class ChatMessageDto {
|
||||||
|
@IsString({ message: 'text must be a string' })
|
||||||
|
@IsNotEmpty({ message: 'text cannot be empty' })
|
||||||
|
@MinLength(1, { message: 'Message is too short' })
|
||||||
|
@MaxLength(500, { message: 'Message cannot exceed 500 characters' })
|
||||||
|
text!: string;
|
||||||
|
}
|
||||||
12
src/chat/dto/join-leave-room.dto.ts
Normal file
12
src/chat/dto/join-leave-room.dto.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { IsNotEmpty, IsString, Matches, MaxLength } from "class-validator";
|
||||||
|
|
||||||
|
export class JoinLeaveRoomDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty({ message: 'roomId cannot be empty' })
|
||||||
|
@MaxLength(50, { message: 'Room name too long (max 50 chars)' })
|
||||||
|
@Matches(/^[a-z0-9-_]+$/, {
|
||||||
|
message:
|
||||||
|
'roomId can only contain lowercase letters, numbers, hyphens and underscores',
|
||||||
|
})
|
||||||
|
roomId!: string;
|
||||||
|
}
|
||||||
13
src/chat/dto/room-message.dto.ts
Normal file
13
src/chat/dto/room-message.dto.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { IsNotEmpty, IsString, Matches, MaxLength, MinLength } from "class-validator";
|
||||||
|
export class RoomMessageDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Matches(/^[a-z0-9-_]+$/, { message: 'Invalid roomId format' })
|
||||||
|
roomId!: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty({ message: 'Message cannot be empty' })
|
||||||
|
@MinLength(1)
|
||||||
|
@MaxLength(500, { message: 'Message cannot exceed 500 characters' })
|
||||||
|
text!: string;
|
||||||
|
}
|
||||||
20
src/chat/guards/ws-jwt.guard.ts
Normal file
20
src/chat/guards/ws-jwt.guard.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
|
import { WsException } from '@nestjs/websockets';
|
||||||
|
import { Socket } from 'socket.io';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WsJwtGuard implements CanActivate {
|
||||||
|
canActivate(context: ExecutionContext): boolean {
|
||||||
|
const client: Socket = context.switchToWs().getClient<Socket>();
|
||||||
|
|
||||||
|
// client.data.user was set in handleConnection() after JWT verification
|
||||||
|
const user = client.data?.user;
|
||||||
|
if (!user) {
|
||||||
|
throw new WsException('Unauthorized — no authenticated user on socket');
|
||||||
|
}
|
||||||
|
|
||||||
|
// optionally check role
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/conversations/conversations.controller.ts
Normal file
34
src/conversations/conversations.controller.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||||
|
import { ConversationsService } from './conversations.service';
|
||||||
|
import { CreateConversationDto } from './dto/create-conversation.dto';
|
||||||
|
import { UpdateConversationDto } from './dto/update-conversation.dto';
|
||||||
|
|
||||||
|
@Controller('conversations')
|
||||||
|
export class ConversationsController {
|
||||||
|
constructor(private readonly conversationsService: ConversationsService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
create(@Body() createConversationDto: CreateConversationDto) {
|
||||||
|
return this.conversationsService.create(createConversationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findAll() {
|
||||||
|
return this.conversationsService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
findOne(@Param('id') id: string) {
|
||||||
|
return this.conversationsService.findOne(+id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
update(@Param('id') id: string, @Body() updateConversationDto: UpdateConversationDto) {
|
||||||
|
return this.conversationsService.update(+id, updateConversationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
remove(@Param('id') id: string) {
|
||||||
|
return this.conversationsService.remove(+id);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/conversations/conversations.module.ts
Normal file
18
src/conversations/conversations.module.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConversationsService } from './conversations.service';
|
||||||
|
import { ConversationsController } from './conversations.controller';
|
||||||
|
import { MongooseModule } from '@nestjs/mongoose';
|
||||||
|
import { Conversation, ConversationSchema } from './schemas/conversation.schema';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports:[
|
||||||
|
MongooseModule.forFeature([
|
||||||
|
{name:Conversation.name,schema:ConversationSchema},
|
||||||
|
|
||||||
|
])
|
||||||
|
],
|
||||||
|
controllers: [ConversationsController],
|
||||||
|
providers: [ConversationsService],
|
||||||
|
exports:[ConversationsService]
|
||||||
|
})
|
||||||
|
export class ConversationsModule {}
|
||||||
34
src/conversations/conversations.service.ts
Normal file
34
src/conversations/conversations.service.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { CreateConversationDto } from './dto/create-conversation.dto';
|
||||||
|
import { UpdateConversationDto } from './dto/update-conversation.dto';
|
||||||
|
import { Conversation, ConversationDocument } from './schemas/conversation.schema';
|
||||||
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
|
import { Model } from 'mongoose';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ConversationsService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@InjectModel(Conversation.name) private userModel: Model<ConversationDocument>,
|
||||||
|
|
||||||
|
) {}
|
||||||
|
create(createConversationDto: CreateConversationDto) {
|
||||||
|
return 'This action adds a new conversation';
|
||||||
|
}
|
||||||
|
|
||||||
|
findAll() {
|
||||||
|
return `This action returns all conversations`;
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(id: number) {
|
||||||
|
return `This action returns a #${id} conversation`;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(id: number, updateConversationDto: UpdateConversationDto) {
|
||||||
|
return `This action updates a #${id} conversation`;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(id: number) {
|
||||||
|
return `This action removes a #${id} conversation`;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/conversations/dto/create-conversation.dto.ts
Normal file
1
src/conversations/dto/create-conversation.dto.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export class CreateConversationDto {}
|
||||||
4
src/conversations/dto/update-conversation.dto.ts
Normal file
4
src/conversations/dto/update-conversation.dto.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateConversationDto } from './create-conversation.dto';
|
||||||
|
|
||||||
|
export class UpdateConversationDto extends PartialType(CreateConversationDto) {}
|
||||||
27
src/conversations/schemas/conversation.schema.ts
Normal file
27
src/conversations/schemas/conversation.schema.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||||
|
|
||||||
|
export type ConversationDocument = Conversation & Document;
|
||||||
|
export enum ConversationType{
|
||||||
|
P2P='p2p',
|
||||||
|
GROUP='group'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema({ timestamps: true })
|
||||||
|
export class Conversation {
|
||||||
|
@Prop({
|
||||||
|
type:String,
|
||||||
|
enum:ConversationType,
|
||||||
|
required:true
|
||||||
|
})
|
||||||
|
type!:ConversationType
|
||||||
|
|
||||||
|
@Prop({type:[String], required:true})
|
||||||
|
participants!:string[]
|
||||||
|
|
||||||
|
@Prop({required:false,default:null})
|
||||||
|
name!:string // for p2p no name
|
||||||
|
|
||||||
|
createdAt!:Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConversationSchema = SchemaFactory.createForClass(Conversation);
|
||||||
33
src/main.ts
33
src/main.ts
@ -1,8 +1,39 @@
|
|||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
app.enableCors({ origin: '*' });
|
||||||
|
app.useGlobalPipes(
|
||||||
|
new ValidationPipe({
|
||||||
|
whitelist:true, // strips unknown properties from @Body()
|
||||||
|
forbidNonWhitelisted:true, // throws 400 if unknown props are sent
|
||||||
|
transform:true // converts query string numbers/booleans from strings → correct types
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const config = new DocumentBuilder()
|
||||||
|
.setTitle('Chat app')
|
||||||
|
.setDescription('The chat API description')
|
||||||
|
.setVersion('1.0')
|
||||||
|
// .addBearerAuth(
|
||||||
|
// {
|
||||||
|
// type: 'http',
|
||||||
|
// scheme: 'bearer',
|
||||||
|
// bearerFormat: 'JWT',
|
||||||
|
// in: 'header',
|
||||||
|
// },
|
||||||
|
// 'jwt',
|
||||||
|
// )
|
||||||
|
.build();
|
||||||
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
|
SwaggerModule.setup('api', app, document);
|
||||||
|
|
||||||
|
const configService=app.get(ConfigService)
|
||||||
|
const port=configService.get<number>('PORT')|| 3000;
|
||||||
|
|
||||||
|
await app.listen(port);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
1
src/messages/dto/create-message.dto.ts
Normal file
1
src/messages/dto/create-message.dto.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export class CreateMessageDto {}
|
||||||
4
src/messages/dto/update-message.dto.ts
Normal file
4
src/messages/dto/update-message.dto.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateMessageDto } from './create-message.dto';
|
||||||
|
|
||||||
|
export class UpdateMessageDto extends PartialType(CreateMessageDto) {}
|
||||||
34
src/messages/messages.controller.ts
Normal file
34
src/messages/messages.controller.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||||
|
import { MessagesService } from './messages.service';
|
||||||
|
import { CreateMessageDto } from './dto/create-message.dto';
|
||||||
|
import { UpdateMessageDto } from './dto/update-message.dto';
|
||||||
|
|
||||||
|
@Controller('messages')
|
||||||
|
export class MessagesController {
|
||||||
|
constructor(private readonly messagesService: MessagesService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
create(@Body() createMessageDto: CreateMessageDto) {
|
||||||
|
return this.messagesService.create(createMessageDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findAll() {
|
||||||
|
return this.messagesService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
findOne(@Param('id') id: string) {
|
||||||
|
return this.messagesService.findOne(+id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
update(@Param('id') id: string, @Body() updateMessageDto: UpdateMessageDto) {
|
||||||
|
return this.messagesService.update(+id, updateMessageDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
remove(@Param('id') id: string) {
|
||||||
|
return this.messagesService.remove(+id);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/messages/messages.module.ts
Normal file
17
src/messages/messages.module.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { MessagesService } from './messages.service';
|
||||||
|
import { MessagesController } from './messages.controller';
|
||||||
|
import { MongooseModule } from '@nestjs/mongoose';
|
||||||
|
import { Message, MessageSchema } from './schemas/message.schema';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports:[
|
||||||
|
MongooseModule.forFeature([
|
||||||
|
{name:Message.name,schema:MessageSchema},
|
||||||
|
])
|
||||||
|
],
|
||||||
|
controllers: [MessagesController],
|
||||||
|
providers: [MessagesService],
|
||||||
|
exports:[MessagesService]
|
||||||
|
})
|
||||||
|
export class MessagesModule {}
|
||||||
30
src/messages/messages.service.ts
Normal file
30
src/messages/messages.service.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { CreateMessageDto } from './dto/create-message.dto';
|
||||||
|
import { UpdateMessageDto } from './dto/update-message.dto';
|
||||||
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
|
import { Message, MessageDocument } from './schemas/message.schema';
|
||||||
|
import { Model } from 'mongoose';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MessagesService {
|
||||||
|
constructor( @InjectModel(Message.name) private MessageModel: Model<MessageDocument>,){}
|
||||||
|
create(createMessageDto: CreateMessageDto) {
|
||||||
|
return 'This action adds a new message';
|
||||||
|
}
|
||||||
|
|
||||||
|
findAll() {
|
||||||
|
return `This action returns all messages`;
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(id: number) {
|
||||||
|
return `This action returns a #${id} message`;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(id: number, updateMessageDto: UpdateMessageDto) {
|
||||||
|
return `This action updates a #${id} message`;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(id: number) {
|
||||||
|
return `This action removes a #${id} message`;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/messages/schemas/message.schema.ts
Normal file
19
src/messages/schemas/message.schema.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||||
|
|
||||||
|
export type MessageDocument = Message & Document;
|
||||||
|
|
||||||
|
@Schema({timestamps:true})
|
||||||
|
export class Message {
|
||||||
|
@Prop({ required:true})
|
||||||
|
conversationId!:string
|
||||||
|
|
||||||
|
@Prop({ required:true})
|
||||||
|
senderId!:string
|
||||||
|
|
||||||
|
@Prop({ required:false})
|
||||||
|
text!:string
|
||||||
|
|
||||||
|
createdAt!:Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MessageSchema = SchemaFactory.createForClass(Message);
|
||||||
9
src/users/dto/create-user.dto.ts
Normal file
9
src/users/dto/create-user.dto.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import {IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CreateUserDto {
|
||||||
|
@ApiProperty({ example: 'Jane Doe', description: 'Full name of the user' })
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name!: string;
|
||||||
|
}
|
||||||
4
src/users/dto/update-user.dto.ts
Normal file
4
src/users/dto/update-user.dto.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateUserDto } from './create-user.dto';
|
||||||
|
|
||||||
|
export class UpdateUserDto extends PartialType(CreateUserDto) {}
|
||||||
11
src/users/schemas/user.schema.ts
Normal file
11
src/users/schemas/user.schema.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||||
|
import { Document } from 'mongoose';
|
||||||
|
|
||||||
|
export type UserDocument = User & Document;
|
||||||
|
|
||||||
|
@Schema({timestamps:true})
|
||||||
|
export class User {
|
||||||
|
@Prop({required:true, unique:true})
|
||||||
|
name!:string
|
||||||
|
}
|
||||||
|
export const UserSchema = SchemaFactory.createForClass(User);
|
||||||
34
src/users/users.controller.ts
Normal file
34
src/users/users.controller.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||||
|
import { UsersService } from './users.service';
|
||||||
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
|
|
||||||
|
@Controller('users')
|
||||||
|
export class UsersController {
|
||||||
|
constructor(private readonly usersService: UsersService) {}
|
||||||
|
|
||||||
|
// @Post()
|
||||||
|
// create(@Body() createUserDto: CreateUserDto) {
|
||||||
|
// return this.usersService.create(createUserDto);
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findAll() {
|
||||||
|
return this.usersService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
findOne(@Param('id') id: string) {
|
||||||
|
return this.usersService.findOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Patch(':id')
|
||||||
|
// update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
|
||||||
|
// return this.usersService.update(+id, updateUserDto);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @Delete(':id')
|
||||||
|
// remove(@Param('id') id: string) {
|
||||||
|
// return this.usersService.remove(+id);
|
||||||
|
// }
|
||||||
|
}
|
||||||
18
src/users/users.module.ts
Normal file
18
src/users/users.module.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { UsersService } from './users.service';
|
||||||
|
import { UsersController } from './users.controller';
|
||||||
|
import { MongooseModule } from '@nestjs/mongoose';
|
||||||
|
import { User, UserSchema } from './schemas/user.schema';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports:[
|
||||||
|
MongooseModule.forFeature([
|
||||||
|
{name:User.name,schema:UserSchema},
|
||||||
|
|
||||||
|
])
|
||||||
|
],
|
||||||
|
controllers: [UsersController],
|
||||||
|
providers: [UsersService],
|
||||||
|
exports:[UsersService]
|
||||||
|
})
|
||||||
|
export class UsersModule {}
|
||||||
33
src/users/users.service.ts
Normal file
33
src/users/users.service.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
|
import { User, UserDocument } from './schemas/user.schema';
|
||||||
|
import { Model } from 'mongoose';
|
||||||
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UsersService {
|
||||||
|
|
||||||
|
constructor(@InjectModel(User.name) private userModel:Model<UserDocument>){
|
||||||
|
|
||||||
|
}
|
||||||
|
async create(createUserDto: CreateUserDto):Promise<UserDocument> {
|
||||||
|
return await this.userModel.create(createUserDto)
|
||||||
|
}
|
||||||
|
|
||||||
|
findAll() {
|
||||||
|
return `This action returns all users`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(name: string):Promise<UserDocument | null> {
|
||||||
|
return await this.userModel.findOne({name}).exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
// update(id: number, updateUserDto: UpdateUserDto) {
|
||||||
|
// return `This action updates a #${id} user`;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// remove(id: number) {
|
||||||
|
// return `This action removes a #${id} user`;
|
||||||
|
// }
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user