-
-
Notifications
You must be signed in to change notification settings - Fork 277
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(messages): add base messages app
- Loading branch information
1 parent
55ebcea
commit 6a4b82e
Showing
25 changed files
with
801 additions
and
25 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { createDbTable } from '../utils'; | ||
|
||
export const createConversationTable = () => { | ||
createDbTable('conversation', (table) => { | ||
table.increments('id').primary(); | ||
table.string('label').notNullable(); | ||
// table.json('messages').notNullable(); // Assuming messages are stored as JSON array of message IDs | ||
// table.json('participants').notNullable(); // Assuming participants are stored as JSON array of simcard IDs | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { createDbTable, DATABASE_PREFIX } from '../utils'; | ||
import { DBInstance } from '../knex'; | ||
import { Message } from '../../../shared/Types'; | ||
|
||
export type InsertMessage = Pick<Message, 'receiver_id' | 'sender_id' | 'content'>; | ||
|
||
export const createMessageTable = () => { | ||
createDbTable('message', (table) => { | ||
table.increments('id').primary(); | ||
table | ||
.integer('sender_id') | ||
.unsigned() | ||
.notNullable() | ||
.references('id') | ||
.inTable(`${DATABASE_PREFIX}sim_card`); | ||
table | ||
.integer('receiver_id') | ||
.unsigned() | ||
.notNullable() | ||
.references('id') | ||
.inTable(`${DATABASE_PREFIX}sim_card`); | ||
table.string('content').notNullable(); | ||
table.dateTime('created_at').notNullable().defaultTo(DBInstance.fn.now()); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { Message, MessageWithPhoneNumbers } from '../../shared/Types'; | ||
import { DBInstance } from '../database/knex'; | ||
import { InsertMessage } from '../database/schemas/Message'; | ||
|
||
const tableName = 'tmp_phone_message'; | ||
|
||
class MessageRepository { | ||
public async getMessages(): Promise<Message[]> { | ||
return await DBInstance(tableName); | ||
} | ||
|
||
public async getMessageById(messageId: number): Promise<Message | null> { | ||
return await DBInstance(tableName).where('id', messageId).first(); | ||
} | ||
|
||
public async getMessagesBySid(sid: number): Promise<MessageWithPhoneNumbers[]> { | ||
return await DBInstance(tableName) | ||
.leftJoin('tmp_phone_sim_card as sender', 'sender.id', 'tmp_phone_message.sender_id') | ||
.leftJoin('tmp_phone_sim_card as receiver', 'receiver.id', 'tmp_phone_message.receiver_id') | ||
.select( | ||
'tmp_phone_message.*', | ||
'sender.phone_number as sender_phone_number', | ||
'receiver.phone_number as receiver_phone_number', | ||
) | ||
.where('sender_id', sid) | ||
.orWhere('receiver_id', sid) | ||
.orderBy('tmp_phone_message.created_at', 'desc'); | ||
} | ||
|
||
public async getMessagesBySenderId(senderId: number): Promise<Message[]> { | ||
return await DBInstance(tableName).where('sender_id', senderId).orderBy('created_at', 'desc'); | ||
} | ||
|
||
public async getMessagesByReceiverId(receiverId: number): Promise<Message[]> { | ||
return await DBInstance(tableName) | ||
.where('receiver_id', receiverId) | ||
.orderBy('created_at', 'desc'); | ||
} | ||
|
||
public async getConversation(senderId: number, receiverId: number): Promise<Message[]> { | ||
return await DBInstance(tableName) | ||
.where('sender_id', senderId) | ||
.andWhere('receiver_id', receiverId) | ||
.orWhere('sender_id', receiverId) | ||
.andWhere('receiver_id', senderId) | ||
.orderBy('created_at', 'asc'); | ||
} | ||
|
||
public async createMessage(message: InsertMessage): Promise<Message> { | ||
const [newId] = await DBInstance(tableName).insert(message); | ||
return await DBInstance(tableName).select('*').where('id', newId).first(); | ||
} | ||
|
||
public async updateMessage(message: Message): Promise<Message> { | ||
await DBInstance(tableName).where('id', message.id).update(message); | ||
return await this.getMessageById(message.id); | ||
} | ||
|
||
public async deleteMessage(messageId: number): Promise<void> { | ||
await DBInstance(tableName).where('id', messageId).delete(); | ||
} | ||
} | ||
|
||
export default new MessageRepository(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Router } from 'fivem-router'; | ||
import { handleError } from '../utils/errors'; | ||
import ConversationService from '../services/ConversationService'; | ||
import z from 'zod'; | ||
|
||
export const conversationsRouter = new Router({ | ||
prefix: '/conversations', | ||
}); | ||
|
||
conversationsRouter.add('/', async (ctx, next) => { | ||
/** Return my messages */ | ||
try { | ||
const conversations = await ConversationService.getMyConversations(ctx); | ||
|
||
ctx.body = { | ||
ok: true, | ||
payload: conversations, | ||
}; | ||
} catch (error) { | ||
handleError(error, ctx); | ||
} | ||
}); | ||
|
||
conversationsRouter.add('/:phoneNumber', async (ctx, next) => { | ||
try { | ||
const { phoneNumber } = z.object({ phoneNumber: z.string().min(2).max(15) }).parse(ctx.params); | ||
|
||
const messages = await ConversationService.getConversation(ctx, phoneNumber); | ||
console.log(messages); | ||
|
||
ctx.body = { | ||
ok: true, | ||
payload: messages, | ||
}; | ||
} catch (error) { | ||
handleError(error, ctx); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { Router } from 'fivem-router'; | ||
import z from 'zod'; | ||
import { handleError } from '../utils/errors'; | ||
import MessageService from '../services/MessageService'; | ||
|
||
export const messagesRouter = new Router({ | ||
prefix: '/messages', | ||
}); | ||
|
||
messagesRouter.add('/', async (ctx, next) => { | ||
/** Return my messages */ | ||
try { | ||
const messages = await MessageService.getMessages(ctx); | ||
|
||
ctx.body = { | ||
ok: true, | ||
payload: messages, | ||
}; | ||
} catch (error) { | ||
handleError(error, ctx); | ||
} | ||
}); | ||
|
||
const sendMessageSchema = z.object({ | ||
content: z.string().min(1).max(255), | ||
phoneNumber: z.string().min(2).max(15), | ||
}); | ||
|
||
messagesRouter.add('/send', async (ctx, next) => { | ||
try { | ||
const { content, phoneNumber } = sendMessageSchema.parse(ctx.request.body); | ||
|
||
// Send message to phoneNumber | ||
const message = await MessageService.sendMessage(ctx, content, phoneNumber); | ||
|
||
ctx.body = { | ||
ok: true, | ||
payload: message, | ||
}; | ||
} catch (error) { | ||
handleError(error, ctx); | ||
} | ||
|
||
await next(); | ||
}); | ||
|
||
messagesRouter.add('/conversation/:phoneNumber', async (ctx, next) => { | ||
try { | ||
const { phoneNumber } = z.object({ phoneNumber: z.string().min(2).max(15) }).parse(ctx.params); | ||
|
||
const messages = await MessageService.getConversation(ctx, phoneNumber); | ||
|
||
ctx.body = { | ||
ok: true, | ||
payload: messages, | ||
}; | ||
} catch (error) { | ||
handleError(error, ctx); | ||
} | ||
|
||
await next(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { RouterContext } from 'fivem-router'; | ||
import { SimCardNotActiveError, SimcardNotFoundError } from '../../shared/Errors'; | ||
import MessageRepository from '../repositories/MessageRepository'; | ||
import SimCardRepository from '../repositories/SimCardRepository'; | ||
import DeviceRepository from '../repositories/DeviceRepository'; | ||
import { Conversation, MessageWithPhoneNumbers } from '../../shared/Types'; | ||
|
||
class ConversationService { | ||
messageRepository: typeof MessageRepository; | ||
simCardRepository: typeof SimCardRepository; | ||
deviceRepository: typeof DeviceRepository; | ||
|
||
constructor( | ||
messageRepository: typeof MessageRepository, | ||
simCardRepository: typeof SimCardRepository, | ||
deviceRepository: typeof DeviceRepository, | ||
) { | ||
this.messageRepository = messageRepository; | ||
this.simCardRepository = simCardRepository; | ||
this.deviceRepository = deviceRepository; | ||
} | ||
|
||
async getMyConversations(ctx: RouterContext): Promise<string[]> { | ||
const messages = await this.messageRepository.getMessagesBySid(ctx.device.sim_card_id); | ||
|
||
const createConversationId = (myNumber: string) => (message: MessageWithPhoneNumbers) => { | ||
const { sender_phone_number, receiver_phone_number } = message; | ||
const [first, second] = [sender_phone_number, receiver_phone_number].sort(); | ||
return first === myNumber ? second : first; | ||
}; | ||
|
||
const conversations = messages.map(createConversationId(ctx.device.phone_number)); | ||
const uniqueConversations = Array.from(new Set(conversations)); | ||
|
||
return uniqueConversations; | ||
} | ||
|
||
async getConversation(ctx: RouterContext, phoneNumber: string) { | ||
const device = await this.deviceRepository.getDeviceById(ctx.device.id); | ||
|
||
if (!device) { | ||
throw new SimcardNotFoundError('SENDER'); | ||
} | ||
|
||
if (!device.sim_card_id) { | ||
throw new SimcardNotFoundError('SENDER'); | ||
} | ||
|
||
const receiverSimcard = await this.simCardRepository.getSimCardByPhoneNumber(phoneNumber); | ||
|
||
if (!receiverSimcard) { | ||
throw new SimcardNotFoundError('RECEIVER'); | ||
} | ||
|
||
if (!receiverSimcard.is_active) { | ||
throw new SimCardNotActiveError('RECEIVER'); | ||
} | ||
|
||
return await this.messageRepository.getConversation(device.sim_card_id, receiverSimcard.id); | ||
} | ||
} | ||
|
||
export default new ConversationService(MessageRepository, SimCardRepository, DeviceRepository); |
Oops, something went wrong.