0.12.18 : add create message, conversation
All checks were successful
Build release Docker image / Build Docker Images (push) Successful in 7m23s

This commit is contained in:
2026-01-16 13:23:27 +01:00
parent 1186463e11
commit 0bb06e0e2d
10 changed files with 1107 additions and 9 deletions

View File

@@ -2,6 +2,36 @@
* chat-conversation controller
*/
import { factories } from '@strapi/strapi'
import { factories } from "@strapi/strapi";
export default factories.createCoreController('api::chat-conversation.chat-conversation');
export default factories.createCoreController(
"api::chat-conversation.chat-conversation",
({ strapi }) => ({
async create(ctx) {
const userId = ctx.state.user?.id;
if (!userId) {
return ctx.unauthorized(
"You must be logged in to create a conversation"
);
}
const { users, title, isGroup } = ctx.request.body.data;
if (!users || !Array.isArray(users) || users.length === 0) {
return ctx.badRequest("userIds must be a non-empty array");
}
try {
const conversation = await strapi
.service("api::chat-conversation.chat-conversation")
.createConversationWithMembers(users, userId, title);
ctx.body = {
data: conversation,
};
} catch (error: any) {
return ctx.badRequest(error.message);
}
},
})
);

View File

@@ -4,4 +4,64 @@
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::chat-conversation.chat-conversation');
export default factories.createCoreService(
'api::chat-conversation.chat-conversation',
({ strapi }) => ({
async createConversationWithMembers(
userIds: number[],
creatorId: number,
title?: string
) {
if (!userIds || userIds.length === 0) {
throw new Error('At least one user ID is required');
}
if (!creatorId) {
throw new Error('Creator ID is required');
}
const uniqueUserIds = [...new Set([...userIds, creatorId])];
for (const userId of uniqueUserIds) {
const userExists = await strapi.db
.query('plugin::users-permissions.user')
.findOne({
where: { id: userId },
});
if (!userExists) {
throw new Error(`User with ID ${userId} not found`);
}
}
const isGroup = uniqueUserIds.length > 2;
const conversation = await strapi.db
.query('api::chat-conversation.chat-conversation')
.create({
data: {
title: isGroup ? title : null,
isGroup,
creator: creatorId,
users: uniqueUserIds.map((id) => ({ id })),
},
});
for (const userId of uniqueUserIds) {
const role = userId === creatorId ? 'owner' : 'member';
await strapi.db
.query('api::chat-conversation-member.chat-conversation-member')
.create({
data: {
user: userId,
conversation: conversation.id,
role,
joinedAt: new Date().toISOString(),
},
});
}
return conversation;
},
})
);

View File

@@ -4,7 +4,8 @@
"info": {
"singularName": "chat-message",
"pluralName": "chat-messages",
"displayName": "ChatMessage"
"displayName": "ChatMessage",
"description": ""
},
"options": {
"draftAndPublish": false
@@ -26,6 +27,16 @@
},
"deletedAt": {
"type": "datetime"
},
"media": {
"allowedTypes": [
"images",
"files",
"videos",
"audios"
],
"type": "media",
"multiple": false
}
}
}

View File

@@ -2,6 +2,87 @@
* chat-message controller
*/
import { factories } from '@strapi/strapi'
import { factories } from "@strapi/strapi";
export default factories.createCoreController('api::chat-message.chat-message');
export default factories.createCoreController(
"api::chat-message.chat-message",
({ strapi }) => ({
async create(ctx) {
const userId = ctx.state.user?.id;
if (!userId) {
return ctx.unauthorized("You must be logged in to send a message");
}
const body = ctx.request.body as any;
const data =
typeof body?.data === "string"
? JSON.parse(body.data)
: body?.data || {};
const { conversation, content, recipientIds } = data;
if (!content || content.trim() === "") {
return ctx.badRequest("Message content is required");
}
let finalConversationId = conversation;
if (!finalConversationId) {
if (
!recipientIds ||
!Array.isArray(recipientIds) ||
recipientIds.length === 0
) {
return ctx.badRequest(
"Either conversationId or recipientIds must be provided"
);
}
try {
const newConversation = await strapi
.service("api::chat-conversation.chat-conversation")
.createConversationWithMembers(recipientIds, userId);
finalConversationId = newConversation.id;
} catch (error: any) {
return ctx.badRequest(
`Failed to create conversation: ${error.message}`
);
}
}
try {
let mediaId: number | undefined;
const mediaInput = (ctx.request.files as any)?.media;
if (mediaInput) {
const file = Array.isArray(mediaInput) ? mediaInput[0] : mediaInput;
const uploaded = await strapi
.plugin("upload")
.service("upload")
.upload({
data: {
fileInfo: {
alternativeText: data?.name || "media",
caption: "media",
name: file.originalFilename || "media",
},
},
files: file,
});
if (uploaded?.[0]?.id) {
mediaId = uploaded[0].id;
}
}
const message = await strapi
.service("api::chat-message.chat-message")
.createMessageInConversation(finalConversationId, userId, content, mediaId);
ctx.body = {
data: message,
};
} catch (error: any) {
return ctx.badRequest(`Failed to create message: ${error.message}`);
}
},
})
);

View File

@@ -4,4 +4,92 @@
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::chat-message.chat-message');
export default factories.createCoreService(
'api::chat-message.chat-message',
({ strapi }) => ({
async createMessageInConversation(
conversationId: number,
senderId: number,
content: string,
mediaId?: number
) {
if (!conversationId) {
throw new Error('Conversation ID is required');
}
if (!senderId) {
throw new Error('Sender ID is required');
}
if (!content || content.trim() === '') {
throw new Error('Message content is required and cannot be empty');
}
const conversation = await strapi.db
.query('api::chat-conversation.chat-conversation')
.findOne({
where: { id: conversationId },
});
if (!conversation) {
throw new Error(`Conversation with ID ${conversationId} not found`);
}
const member = await strapi.db
.query('api::chat-conversation-member.chat-conversation-member')
.findOne({
where: {
user: { id: senderId },
conversation: { id: conversationId },
},
});
if (!member) {
throw new Error(
'User is not a member of this conversation'
);
}
if (mediaId) {
const media = await strapi.db
.query('plugin::upload.file')
.findOne({
where: { id: mediaId },
});
if (!media) {
throw new Error(`Media with ID ${mediaId} not found`);
}
}
const messageData: any = {
sender: senderId,
content: content.trim(),
isEdited: false,
};
if (mediaId) {
messageData.media = mediaId;
}
const message = await strapi.db
.query('api::chat-message.chat-message')
.create({
data: messageData,
});
await strapi.db
.query('api::chat-conversation.chat-conversation')
.update({
where: { id: conversationId },
data: {
messages: {
connect: [{ id: message.id }],
},
},
});
return message;
},
})
);

View File

@@ -14,7 +14,7 @@
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
},
"x-generation-date": "2026-01-15T08:01:40.043Z"
"x-generation-date": "2026-01-16T10:01:51.447Z"
},
"x-strapi-config": {
"plugins": [
@@ -44201,6 +44201,135 @@
"type": "string",
"format": "date-time"
},
"media": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
},
"name": {
"type": "string"
},
"alternativeText": {
"type": "string"
},
"caption": {
"type": "string"
},
"width": {
"type": "integer"
},
"height": {
"type": "integer"
},
"formats": {},
"hash": {
"type": "string"
},
"ext": {
"type": "string"
},
"mime": {
"type": "string"
},
"size": {
"type": "number",
"format": "float"
},
"url": {
"type": "string"
},
"previewUrl": {
"type": "string"
},
"provider": {
"type": "string"
},
"provider_metadata": {},
"related": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
}
},
"folder": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
},
"folderPath": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
},
"publishedAt": {
"type": "string",
"format": "date-time"
},
"createdBy": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
},
"updatedBy": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
},
"locale": {
"type": "string"
},
"localizations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
}
}
}
},
"createdAt": {
"type": "string",
"format": "date-time"
@@ -48258,6 +48387,135 @@
"type": "string",
"format": "date-time"
},
"media": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
},
"name": {
"type": "string"
},
"alternativeText": {
"type": "string"
},
"caption": {
"type": "string"
},
"width": {
"type": "integer"
},
"height": {
"type": "integer"
},
"formats": {},
"hash": {
"type": "string"
},
"ext": {
"type": "string"
},
"mime": {
"type": "string"
},
"size": {
"type": "number",
"format": "float"
},
"url": {
"type": "string"
},
"previewUrl": {
"type": "string"
},
"provider": {
"type": "string"
},
"provider_metadata": {},
"related": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
}
},
"folder": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
},
"folderPath": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
},
"publishedAt": {
"type": "string",
"format": "date-time"
},
"createdBy": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
},
"updatedBy": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
},
"locale": {
"type": "string"
},
"localizations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
}
}
}
},
"createdAt": {
"type": "string",
"format": "date-time"
@@ -48565,6 +48823,17 @@
"type": "string",
"format": "date-time"
},
"media": {
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"example": "string or id"
},
"locale": {
"type": "string"
},
@@ -52286,6 +52555,135 @@
"type": "string",
"format": "date-time"
},
"media": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
},
"name": {
"type": "string"
},
"alternativeText": {
"type": "string"
},
"caption": {
"type": "string"
},
"width": {
"type": "integer"
},
"height": {
"type": "integer"
},
"formats": {},
"hash": {
"type": "string"
},
"ext": {
"type": "string"
},
"mime": {
"type": "string"
},
"size": {
"type": "number",
"format": "float"
},
"url": {
"type": "string"
},
"previewUrl": {
"type": "string"
},
"provider": {
"type": "string"
},
"provider_metadata": {},
"related": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
}
},
"folder": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
},
"folderPath": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
},
"publishedAt": {
"type": "string",
"format": "date-time"
},
"createdBy": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
},
"updatedBy": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
},
"locale": {
"type": "string"
},
"localizations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
}
}
}
},
"createdAt": {
"type": "string",
"format": "date-time"
@@ -52355,6 +52753,135 @@
"type": "string",
"format": "date-time"
},
"media": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
},
"name": {
"type": "string"
},
"alternativeText": {
"type": "string"
},
"caption": {
"type": "string"
},
"width": {
"type": "integer"
},
"height": {
"type": "integer"
},
"formats": {},
"hash": {
"type": "string"
},
"ext": {
"type": "string"
},
"mime": {
"type": "string"
},
"size": {
"type": "number",
"format": "float"
},
"url": {
"type": "string"
},
"previewUrl": {
"type": "string"
},
"provider": {
"type": "string"
},
"provider_metadata": {},
"related": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
}
},
"folder": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
},
"folderPath": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
},
"publishedAt": {
"type": "string",
"format": "date-time"
},
"createdBy": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
},
"updatedBy": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
},
"locale": {
"type": "string"
},
"localizations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"documentId": {
"type": "string"
}
}
}
}
}
},
"createdAt": {
"type": "string",
"format": "date-time"