This commit is contained in:
2026-02-23 23:56:20 +03:00
parent d675d3ab9f
commit 6402b4cfb1
5 changed files with 238 additions and 118 deletions

View File

@@ -14,6 +14,7 @@ service MessageService {
rpc GetUserByToken(GetUserByTokenRequest) returns(GetUserResponse); rpc GetUserByToken(GetUserByTokenRequest) returns(GetUserResponse);
rpc GetUserByEmail(GetUserByEmailRequest) returns(GetUserResponse); rpc GetUserByEmail(GetUserByEmailRequest) returns(GetUserResponse);
rpc ListUser(ListUserRequest) returns(ListUserResponse); rpc ListUser(ListUserRequest) returns(ListUserResponse);
rpc GetUser(GetUserRequest) returns(GetUserResponse);
rpc CreateChat(CreateChatRequest) returns(CreateChatResponse); rpc CreateChat(CreateChatRequest) returns(CreateChatResponse);
rpc UpdateChat(UpdateChatRequest) returns(UpdateChatResponse); rpc UpdateChat(UpdateChatRequest) returns(UpdateChatResponse);
@@ -31,7 +32,10 @@ service MessageService {
// User // User
message CreateUserRequest { message CreateUserRequest {
string email = 1 [(validate.rules).string.email = true]; string email = 1 [(validate.rules).string.email = true];
optional string name = 2; optional string name = 2 [(validate.rules).string = {
ignore_empty: true, // ⭐ Главное правило
max_len: 200
}];
} }
message CreateUserResponse { message CreateUserResponse {
@@ -41,7 +45,10 @@ message CreateUserResponse {
message UpdateUserRequest { message UpdateUserRequest {
int32 id = 1 [(validate.rules).int32.gt = 0]; int32 id = 1 [(validate.rules).int32.gt = 0];
optional string token = 2; // todo optional string token = 2; // todo
optional string description = 3; optional string description = 3 [(validate.rules).string = {
ignore_empty: true,
max_len: 200
}];
} }
message UpdateUserResponse { message UpdateUserResponse {
@@ -56,6 +63,10 @@ message GetUserByEmailRequest {
string email = 1 [(validate.rules).string.email = true]; string email = 1 [(validate.rules).string.email = true];
} }
message GetUserRequest {
int32 id = 1 [(validate.rules).int32.gt = 0];
}
message GetUserResponse { message GetUserResponse {
User data = 1; User data = 1;
} }
@@ -91,11 +102,13 @@ message UserForChatResponse {
// Chat // Chat
message CreateChatRequest { message CreateChatRequest {
optional string name = 1; optional string name = 1;
repeated UserForChat users = 2; repeated UserForChat users = 2 [(validate.rules).repeated = {
min_items: 1,
}];
} }
message UserForChat { message UserForChat {
int32 user_id = 1; int32 user_id = 1 [(validate.rules).int32.gt = 0];
optional bool is_admin = 2; optional bool is_admin = 2;
} }
@@ -105,11 +118,11 @@ message CreateChatResponse {
message UpdateChatRequest { message UpdateChatRequest {
string id = 1 [(validate.rules).string = { string id = 1 [(validate.rules).string = {
pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", uuid: true,
min_len: 36, }];
max_len: 36 repeated UserForChat users = 2 [(validate.rules).repeated = {
min_items: 1,
}]; }];
repeated UserForChat users = 2;
} }
message UpdateChatResponse { message UpdateChatResponse {
@@ -117,7 +130,9 @@ message UpdateChatResponse {
} }
message GetChatRequest { message GetChatRequest {
string id = 1; string id = 1 [(validate.rules).string = {
uuid: true,
}];
} }
message GetChatResponse { message GetChatResponse {
@@ -144,15 +159,25 @@ message Chat {
// Message // Message
message CreateMessageRequest { message CreateMessageRequest {
string chat_id = 2 [(validate.rules).string = { string chat_id = 2 [(validate.rules).string = {
pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", uuid: true,
min_len: 36,
max_len: 36
}]; }];
int32 user_id = 3 [(validate.rules).int32.gt = 0]; int32 user_id = 3 [(validate.rules).int32.gt = 0];
optional string text = 4; optional string text = 4 [(validate.rules).string = {
optional string image = 5; ignore_empty: true,
optional string video = 6; max_len: 200
optional string file = 7; }];
optional string image = 5 [(validate.rules).string = {
ignore_empty: true,
max_len: 200
}];
optional string video = 6 [(validate.rules).string = {
ignore_empty: true,
max_len: 200
}];
optional string file = 7 [(validate.rules).string = {
ignore_empty: true,
max_len: 200
}];
} }
message CreateMessageResponse { message CreateMessageResponse {
@@ -162,14 +187,24 @@ message CreateMessageResponse {
message UpdateMessageRequest { message UpdateMessageRequest {
int32 id = 1 [(validate.rules).int32.gt = 0]; int32 id = 1 [(validate.rules).int32.gt = 0];
string chat_id = 2 [(validate.rules).string = { string chat_id = 2 [(validate.rules).string = {
pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", uuid: true,
min_len: 36, }];
max_len: 36 optional string text = 3 [(validate.rules).string = {
ignore_empty: true,
max_len: 200
}];
optional string image = 4 [(validate.rules).string = {
ignore_empty: true,
max_len: 200
}];
optional string video = 5 [(validate.rules).string = {
ignore_empty: true,
max_len: 200
}];
optional string file = 6 [(validate.rules).string = {
ignore_empty: true,
max_len: 200
}]; }];
optional string text = 3;
optional string image = 4;
optional string video = 5;
optional string file = 6;
} }
message UpdateMessageResponse { message UpdateMessageResponse {
@@ -187,9 +222,7 @@ message GetMessageResponse {
message ListMessageRequest { message ListMessageRequest {
int32 page = 1; int32 page = 1;
string chat_id = 2 [(validate.rules).string = { string chat_id = 2 [(validate.rules).string = {
pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", uuid: true,
min_len: 36,
max_len: 36
}]; }];
} }

View File

@@ -22,6 +22,10 @@ interface GetUserByEmailDto {
email: string email: string
} }
interface GetUserByIDDto {
id: number
}
interface ListUserDto { interface ListUserDto {
page: number page: number
iser_ids: number[] iser_ids: number[]
@@ -32,7 +36,7 @@ interface CreateChatDto {
users: { user_id: number; is_admin?: boolean }[] users: { user_id: number; is_admin?: boolean }[]
} }
interface UpdateChat { interface UpdateChatDto {
id: number id: number
users: { user_id: number; is_admin?: boolean }[] users: { user_id: number; is_admin?: boolean }[]
} }
@@ -70,11 +74,6 @@ interface ListMessageDto {
chat_id: string chat_id: string
} }
interface Version {
id: number
version: string
}
interface VersionDto {} interface VersionDto {}
export interface User { export interface User {
@@ -103,53 +102,38 @@ interface Message {
updated_at: string updated_at: string
} }
interface Response<T> { interface Version {
data: T id: number
version: string
}
interface Callback<T> {
(error: Error, res: { data: T }): void
} }
interface Services { interface Services {
createUser: (data: CreateUserDto) => { data: User } createUser: (data: CreateUserDto, cb: Callback<User>) => void
updateUser: (data: UpdateUserDto) => { data: User } updateUser: (data: UpdateUserDto, cb: Callback<User>) => void
getUserByToken: (data: GetUserByTokenDto) => { data: User } getUserByToken: (data: GetUserByTokenDto, cb: Callback<User>) => void
getUserByEmail: (data: GetUserByEmailDto) => { data: User } getUserByEmail: (data: GetUserByEmailDto, cb: Callback<User>) => void
listUser: (data: ListUserDto) => { data: User[] } getUser: (data: GetUserByIDDto, cb: Callback<User>) => void
createChat: (data: CreateChatDto) => { data: Chat } listUser: (data: ListUserDto, cb: Callback<User[]>) => void
updateChat: (data: UpdateChat) => { data: Chat } createChat: (data: CreateChatDto, cb: Callback<Chat>) => void
getChat: (data: GetChatDto) => { data: Chat } updateChat: (data: UpdateChatDto, cb: Callback<Chat>) => void
listChat: (data: ListChatDto) => { data: Chat[] } getChat: (data: GetChatDto, cb: Callback<Chat>) => void
createMessage: (data: CreateMessageDto) => { data: Message } listChat: (data: ListChatDto, cb: Callback<Chat[]>) => void
updateMessage: (data: UpdateMessageDto) => { data: Message } createMessage: (data: CreateMessageDto, cb: Callback<Message>) => void
getMessage: (data: GetMessageDto) => { data: Message } updateMessage: (data: UpdateMessageDto, cb: Callback<Message>) => void
listMessage: (data: ListMessageDto) => { data: Message[] } getMessage: (data: GetMessageDto, cb: Callback<Message>) => void
getVersion: (data: VersionDto) => { data: Version } listMessage: (data: ListMessageDto, cb: Callback<Message[]>) => void
} getVersion: (data: VersionDto, cb: Callback<Version>) => void
enum ServiceName {
CreateUser = 'createUser',
UpdateUser = 'updateUser',
GetUserByToken = 'getUserByToken',
GetUserByEmail = 'getUserByEmail',
ListUser = 'listUser',
CreateChat = 'createChat',
UpdateChat = 'updateChat',
GetChat = 'getChat',
ListChat = 'listChat',
CreateMessage = 'createMessage',
UpdateMessage = 'updateMessage',
GetMessage = 'getMessage',
ListMessage = 'listMessage',
GetVersion = 'getVersion',
} }
class GrpcClient { class GrpcClient {
messageClient: Services private messageClient: Services
constructor() { constructor() {
console.log('Grpc Client init') console.log('Grpc Client init')
const PROTO_PATH = path.resolve('./proto/message.proto') const PROTO_PATH = path.resolve('./proto/message.proto')
const packageDefinition = protoLoader.loadSync(PROTO_PATH, { const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true, keepCase: true,
@@ -161,43 +145,147 @@ class GrpcClient {
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) const protoDescriptor = grpc.loadPackageDefinition(packageDefinition)
const messageProto = protoDescriptor.message as any const messageProto = protoDescriptor.message as any
this.messageClient = new messageProto.MessageService('localhost:8070', grpc.credentials.createInsecure()) this.messageClient = new messageProto.MessageService('localhost:8070', grpc.credentials.createInsecure())
// console.log(this.messageClient)
} }
private toPromise<T, R>(client: any, methodName: ServiceName) { // User
const service = client[methodName] createUser(dto: CreateUserDto): Promise<{ data: User }> {
return new Promise((resolve, reject) => {
return (request: T): Promise<Response<R>> => { this.messageClient.createUser(dto, (error, data) => {
return new Promise((resolve, reject) => { if (error) reject(error?.message)
service(request, (error: Error, response: Response<R>) => { else resolve(data)
if (error) reject(error.message)
else resolve(response)
})
}) })
} })
} }
getVersion(dto: VersionDto) { updateUser(dto: UpdateUserDto): Promise<{ data: User }> {
const service = this.messageClient[ServiceName.GetVersion] return new Promise((resolve, reject) => {
this.messageClient.updateUser(dto, (error, data) => {
return this.toPromise<VersionDto, Version>(this.messageClient, ServiceName.GetVersion)(dto) if (error) reject(error?.message)
else resolve(data)
})
})
} }
// getUserByEmail(dto: GetUserByEmail) { getUserByToken(dto: GetUserByTokenDto): Promise<{ data: User }> {
// return this.toPromise<GetUserByEmail, User>(this.messageClient, ServiceName.GetUserByEmail)(dto) return new Promise((resolve, reject) => {
// } this.messageClient.getUserByToken(dto, (error, data) => {
if (error) reject(error?.message)
getChatsByUser(dto: ListChatDto) { else resolve(data)
return this.toPromise<ListChatDto, Chat[]>(this.messageClient, ServiceName.ListChat)(dto) })
})
} }
getMessagesByChatId(dto: ListMessageDto) { getUserByEmail(dto: GetUserByEmailDto): Promise<{ data: User }> {
return this.toPromise<ListMessageDto, Message[]>(this.messageClient, ServiceName.ListMessage)(dto) return new Promise((resolve, reject) => {
this.messageClient.getUserByEmail(dto, (error, data) => {
if (error) reject(error?.message)
else resolve(data)
})
})
} }
createMessage(dto: CreateMessageDto) { getUser(dto: GetUserByIDDto): Promise<{ data: User }> {
return this.toPromise<CreateMessageDto, Message>(this.messageClient, ServiceName.CreateMessage)(dto) return new Promise((resolve, reject) => {
this.messageClient.getUser(dto, (error, data) => {
if (error) reject(error?.message)
else resolve(data)
})
})
}
listUser(dto: ListUserDto): Promise<{ data: User[] }> {
return new Promise((resolve, reject) => {
this.messageClient.listUser(dto, (error, data) => {
if (error) reject(error?.message)
else resolve(data)
})
})
}
// Chat
createChat(dto: CreateChatDto): Promise<{ data: Chat }> {
return new Promise((resolve, reject) => {
this.messageClient.createChat(dto, (error, data) => {
if (error) reject(error?.message)
else resolve(data)
})
})
}
updateChat(dto: UpdateChatDto): Promise<{ data: Chat }> {
return new Promise((resolve, reject) => {
this.messageClient.updateChat(dto, (error, data) => {
if (error) reject(error?.message)
else resolve(data)
})
})
}
getChat(dto: GetChatDto): Promise<{ data: Chat }> {
return new Promise((resolve, reject) => {
this.messageClient.getChat(dto, (error, data) => {
if (error) reject(error?.message)
else resolve(data)
})
})
}
listChat(dto: ListChatDto): Promise<{ data: Chat[] }> {
return new Promise((resolve, reject) => {
this.messageClient.listChat(dto, (error, data) => {
if (error) reject(error?.message)
else resolve(data)
})
})
}
// message
createMessage(dto: CreateMessageDto): Promise<{ data: Message }> {
return new Promise((resolve, reject) => {
this.messageClient.createMessage(dto, (error, data) => {
if (error) reject(error?.message)
else resolve(data)
})
})
}
updateMessage(dto: UpdateMessageDto): Promise<{ data: Message }> {
return new Promise((resolve, reject) => {
this.messageClient.updateMessage(dto, (error, data) => {
if (error) reject(error?.message)
else resolve(data)
})
})
}
getMessage(dto: GetMessageDto): Promise<{ data: Message }> {
return new Promise((resolve, reject) => {
this.messageClient.getMessage(dto, (error, data) => {
if (error) reject(error?.message)
else resolve(data)
})
})
}
listMessage(dto: ListMessageDto): Promise<{ data: Message[] }> {
return new Promise((resolve, reject) => {
this.messageClient.listMessage(dto, (error, data) => {
if (error) reject(error?.message)
else resolve(data)
})
})
}
// version
getVersion(dto: VersionDto): Promise<{ data: Version }> {
return new Promise((resolve, reject) => {
this.messageClient.getVersion(dto, (error, data) => {
if (error) reject(error?.message)
else resolve(data)
})
})
} }
} }

View File

@@ -2,34 +2,35 @@ import { HttpStatusCodes } from './constants.ts'
import { errors } from 'jose' import { errors } from 'jose'
import type { LoginDto, WebSocketData } from './types/types.ts' import type { LoginDto, WebSocketData } from './types/types.ts'
import { createAccessToken, verifyAccessToken } from './utils/jwt.ts' import { createAccessToken, verifyAccessToken } from './utils/jwt.ts'
import { grpcClient } from './grpc/client.ts' import { grpcClient as client } from './grpc/client.ts'
import { config } from './config.ts' import { config } from './config.ts'
export async function login(req: Request) { export async function login(req: Request) {
try { try {
const body: LoginDto = await req.json() const body: LoginDto = await req.json()
const versionResponse = await grpcClient.getVersion({}) const versionResponse = await client.getVersion({})
console.log(versionResponse.data) console.log(versionResponse?.data)
const { email } = body const { email } = body
if (!email) return Response.json({ message: 'email required' }, { status: HttpStatusCodes.BAD_REQUEST }) if (!email) return Response.json({ message: 'email required' }, { status: HttpStatusCodes.BAD_REQUEST })
const userResponse = await grpcClient.getUserByEmail({ email: 'vadim.olonin@gmail.com' }) const userResponse = await client.getUserByEmail({ email: body.email })
const user = userResponse.data const user = userResponse.data
if (!user) return Response.json({ message: 'Invalid email or password' }, { status: HttpStatusCodes.NOT_FOUND }) if (!user) return Response.json({ message: 'Invalid email or password' }, { status: HttpStatusCodes.NOT_FOUND })
const accessToken = await createAccessToken(user.id, user.email) const accessToken = await createAccessToken(user.id, user.email)
const expires = new Date(Date.now() + config.cookieExpiry * 1000) const expires = new Date(Date.now() + config.cookieExpiry * 1000)
const sessionCookie = new Bun.Cookie('token', accessToken.token, { // const sessionCookie = new Bun.Cookie('token', accessToken.token, {
path: '/', // path: '/',
expires: expires, // expires: expires,
// maxAge: config.cookieExpiry, // maxAge: config.cookieExpiry,
httpOnly: true, // httpOnly: true,
// secure: true, // secure: true,
sameSite: 'strict', // sameSite: 'strict',
}) // })
return Response.json( return Response.json(
{ {

View File

@@ -3,7 +3,6 @@ import type { WebSocketData, WsData } from './types/types.ts'
import { grpcClient as client } from './grpc/client.ts' import { grpcClient as client } from './grpc/client.ts'
import { login, upgrade } from './handles.ts' import { login, upgrade } from './handles.ts'
const GROUP = 'group'
const PORT = 3000 const PORT = 3000
const server = Bun.serve({ const server = Bun.serve({
@@ -28,12 +27,11 @@ const server = Bun.serve({
const user = ws.data const user = ws.data
const chatResponse = await client.getChatsByUser({ page: 0, user_ids: [1] }) const userResponse = await client.getUser({ id: user.userId })
ws.send(JSON.stringify({ type: 'USER', ...userResponse }))
const chatResponse = await client.listChat({ page: 0, user_id: user.userId })
chatResponse.data.forEach((el) => ws.subscribe(el.id)) chatResponse.data.forEach((el) => ws.subscribe(el.id))
// console.log('chats', chatResponse.data)
// console.log('subscriptions', ws.subscriptions)
ws.send(JSON.stringify({ type: 'CHATS', ...chatResponse })) ws.send(JSON.stringify({ type: 'CHATS', ...chatResponse }))
} catch (error) { } catch (error) {
console.log(error) console.log(error)
@@ -59,20 +57,20 @@ const server = Bun.serve({
return return
} }
if (o.type === 'MESSAGE_CREATE') { if (o.type === 'CREATE_MESSAGE') {
console.log('create') console.log('create')
const message = await client.createMessage({ const messageResponse = await client.createMessage({
chat_id: o.data.chat_id, chat_id: o.data.chat_id,
user_id: ws.data.userId, user_id: ws.data.userId,
text: o.data.text, text: o.data.text,
}) })
server.publish(o.data.chat_id, JSON.stringify({ type: 'CREATE_MESSAGE', ...message })) server.publish(o.data.chat_id, JSON.stringify({ type: 'MESSAGE', ...messageResponse }))
} }
if (o.type === 'GET_MESSAGES') { if (o.type === 'GET_MESSAGES') {
console.log('GET_MESSAGES') console.log('GET_MESSAGES')
const messages = await client.getMessagesByChatId({ chat_id: o.data.chat_id, page: 1 }) const messages = await client.listMessage({ chat_id: o.data.chat_id, page: 1 })
server.publish(o.data.chat_id, JSON.stringify({ type: 'MESSAGES', ...messages })) server.publish(o.data.chat_id, JSON.stringify({ type: 'MESSAGES', ...messages }))
} }
} }

View File

@@ -26,7 +26,7 @@ interface ListMessages {
} }
interface CreateMessage { interface CreateMessage {
type: 'MESSAGE_CREATE' type: 'CREATE_MESSAGE'
data: { data: {
chat_id: string chat_id: string
text: string text: string