Allmine API

Messages v2 foto — RTK Query

~5 dkMobil / WebKararlı

Mesajlaşma v2 fotoğraf gönderimi mobil

Mesajlasma V2 - React Native (RTK Query) Kullanim ve Migration Rehberi

Migration rehberi

Yapılandırma sırası için Migration şablonu. Yeni düzenlemelerde Amaç → Önkoşullar → Endpoint → Request/Response → Hata kodları → Client adımları → İlgili sayfalar bölümlerini tercih edin.

Bu doküman, mesaj gönderme akisinin v2ye alınmasi ve fotograf gönderiminin mobil tarafta (React Native + RTK Query) entegrasyonu için hazırlandı.

1) Kisa Ozet

  • Yeni route acilmadi, ayni endpointin v2 versiyonu eklendi.
  • Mesaj gönderimi:
    • POST /api/v1/messages -> mevcut davranis (geri uyumluluk)
    • POST /api/v2/messages -> yeni davranis (text + fotograf)
  • Chat gecmisi endpointi degismedi:
    • GET /api/v1/messages/chat/:chatId
  • Socket eventi degismedi:
    • /chat namespace, newMessage eventi

2) V2 Backend Sozlesmesi

2.1 Endpoint

  • URL: POST /api/v2/messages
  • Auth: Bearer <JWT> zorunlu
  • Content-Type:
    • Text mesaj için application/json
    • Fotograf mesaji için multipart/form-data

2.2 Request Kurallari

  • Ortak alanlar:
    • type: 'text' | 'media'
    • chatId?: mevcut sohbet ID
    • targetUserId?: sohbet yoksa hedef kullanıcı ID
    • content?: text icerigi veya media caption
  • Is kurallari:
    • chatId yoksa targetUserId zorunlu
    • type='text':
      • content zorunlu
      • file olmamali
    • type='media':
      • file zorunlu (tek dosya)
      • content opsiyonel (caption)

2.3 Fotograf Dogrulama ve Optimizasyon

  • Izinli mime type:
    • image/jpeg
    • image/jpg
    • image/png
    • image/webp
  • Maksimum dosya boyutu: 10MB
  • Sunucu tarafi optimizasyon:
    • EXIF orientation duzeltme
    • Max uzun kenar: 2048
    • Cikti formati: webp (quality=82)
  • Depolama yolu: messages/photos

2.4 Ornek Response (MessageResponseDto)

{
  "_id": "67e4f4f0d56e3a0ddbfab123",
  "chatId": "67e4f3ffd56e3a0ddbfab111",
  "senderId": "67e4f2aed56e3a0ddbfab001",
  "type": "media",
  "content": "aksamdan bir kare",
  "media": {
    "url": "https://cdn.example.com/messages/photos/8c7f.webp",
    "type": "image",
    "name": "IMG_1123.jpg",
    "size": 148220,
    "width": 1440,
    "height": 1080
  },
  "creditTransfer": null,
  "readBy": ["67e4f2aed56e3a0ddbfab001"],
  "createdAt": "2026-03-09T10:10:10.000Z",
  "updatedAt": "2026-03-09T10:10:10.000Z"
}

3) RTK Query Entegrasyonu (React Native)

3.1 Tipler

export type MessageType = 'text' | 'media' | 'credit_transfer';

export type MessageMedia = {
  url: string;
  type: 'image' | 'video' | 'audio' | 'file';
  name?: string;
  size?: number;
  duration?: number;
  width?: number;
  height?: number;
  thumbnail_url?: string;
};

export type Message = {
  _id: string;
  chatId: string;
  senderId: string;
  type: MessageType;
  content?: string | null;
  media?: MessageMedia | null;
  creditTransfer?: {
    amount: number;
    recipientId: string;
    senderId: string;
  } | null;
  readBy: string[];
  createdAt: string;
  updatedAt: string;
};

export type MessageListResponse = {
  messages: Message[];
  participants: Array<{
    _id: string;
    username?: string;
  }>;
  page: number;
  limit: number;
  total: number;
  totalPages: number;
  hasNextPage: boolean;
  hasPreviousPage: boolean;
};

export type BaseResponseDto<T> = {
  isSuccess: boolean;
  statusCode: number;
  data: T;
  errors?: string[];
  timestamp: string;
};

3.2 Base API (ornek)

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import type { RootState } from '../store';

export const baseApi = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({
    baseUrl: `${process.env.EXPO_PUBLIC_API_URL}/api`,
    prepareHeaders: (headers, { getState }) => {
      const token = (getState() as RootState).auth.accessToken;
      if (token) headers.set('Authorization', `Bearer ${token}`);
      return headers;
    },
  }),
  tagTypes: ['Messages'],
  endpoints: () => ({}),
});

3.3 Messages API (v2 send + mevcut history)

import { baseApi } from './baseApi';
import type { BaseResponseDto, Message, MessageListResponse } from '@/types/messages';

type GetChatMessagesArgs = {
  chatId: string;
  page?: number;
  limit?: number;
};

type SendTextMessageV2Args = {
  chatId?: string;
  targetUserId?: string;
  content: string;
};

type SendPhotoMessageV2Args = {
  chatId?: string;
  targetUserId?: string;
  content?: string; // caption
  file: {
    uri: string;
    name: string;
    type: 'image/jpeg' | 'image/jpg' | 'image/png' | 'image/webp';
  };
};

const unwrap = <T>(response: BaseResponseDto<T> | T): T => {
  if (response && typeof response === 'object' && 'data' in (response as any)) {
    return (response as BaseResponseDto<T>).data;
  }
  return response as T;
};

export const messagesApi = baseApi.injectEndpoints({
  endpoints: (builder) => ({
    getChatMessages: builder.query<MessageListResponse, GetChatMessagesArgs>({
      query: ({ chatId, page = 1, limit = 20 }) => ({
        url: `v1/messages/chat/${chatId}`,
        params: { page, limit },
      }),
      transformResponse: (res: BaseResponseDto<MessageListResponse> | MessageListResponse) =>
        unwrap(res),
      providesTags: (_result, _error, arg) => [{ type: 'Messages', id: arg.chatId }],
    }),

    sendTextMessageV2: builder.mutation<Message, SendTextMessageV2Args>({
      query: (body) => ({
        url: 'v2/messages',
        method: 'POST',
        body: {
          type: 'text',
          chatId: body.chatId,
          targetUserId: body.targetUserId,
          content: body.content,
        },
      }),
      transformResponse: (res: BaseResponseDto<Message> | Message) => unwrap(res),
      invalidatesTags: (result) =>
        result?.chatId ? [{ type: 'Messages', id: result.chatId }] : [],
    }),

    sendPhotoMessageV2: builder.mutation<Message, SendPhotoMessageV2Args>({
      query: ({ chatId, targetUserId, content, file }) => {
        const form = new FormData();
        form.append('type', 'media');
        if (chatId) form.append('chatId', chatId);
        if (targetUserId) form.append('targetUserId', targetUserId);
        if (content) form.append('content', content);
        form.append('file', file as any);

        return {
          url: 'v2/messages',
          method: 'POST',
          body: form,
        };
      },
      transformResponse: (res: BaseResponseDto<Message> | Message) => unwrap(res),
      invalidatesTags: (result) =>
        result?.chatId ? [{ type: 'Messages', id: result.chatId }] : [],
    }),
  }),
});

export const {
  useGetChatMessagesQuery,
  useSendTextMessageV2Mutation,
  useSendPhotoMessageV2Mutation,
} = messagesApi;

3.4 React Native kritik not (FormData)

  • FormData ile dosya gönderirken Content-Type header'ini elle set etmeyin.
  • fetchBaseQuery boundary'yi kendışı olusturur.
  • RN image picker cevabindan uri, name, type map edilmelidir.

Ornek map:

const photoFile = {
  uri: asset.uri,
  name: asset.fileName ?? `photo-${Date.now()}.jpg`,
  type: (asset.type ?? 'image/jpeg') as 'image/jpeg' | 'image/jpg' | 'image/png' | 'image/webp',
};

4) Migration Plani (v1 -> v2)

4.1 Etkilenen akislari ayir

  • Degismeyenler
    • GET /api/v1/messages/chat/:chatId
    • Socket newMessage dinleme
    • POST /api/v1/messages/credit-transfer
  • Degisen
    • Mesaj gönderme mutasyonu (artık v2/messages)

4.2 API katmani migration adimlari

  1. Eski sendMessage mutasyonunu kaldirmayin; once sendTextMessageV2 ve sendPhotoMessageV2 ekleyin.
  2. Composer UI'yi yeni mutasyonlara baglayin:
    • text send -> sendTextMessageV2
    • image send -> sendPhotoMessageV2
  3. Production rollout'ta gecici fallback birakin:
    • v2 hata alınirsa text için bir kere v1/messages retry (opsiyonel)
  4. Stabil olduktan sonra v1 send mutasyonunu kaldirin.

4.3 UI migration checklist

  • Composer'a image picker aksiyonu ekle
  • Gonderim oncesi local validation:
    • mime type kontrolu
    • 10MB boyut kontrolu
  • media mesaj render'i:
    • message.type === 'media' && message.media?.type === 'image'
    • content varsa caption olarak göster

4.4 Hata kodlari ve beklenen davranis

  • 400 Bad Request
    • content bos (text mesajda)
    • media için file yok
    • chatId ve targetUserId ikisi de yok
    • gecersiz mime type / 10MB ustu dosya
  • 403 Forbidden
    • follow/block kurali nedeniyle gönderim engeli
  • 404 Not Found
    • chat bulunamadi

UI tarafında 400 hatalarini kullanıcıya dogrudan okunur mesajla gösterin, 5xx hatalarda generic retry/snackbar kullanın.

5) E2E Test Senaryolari (Mobil)

  1. Var olan chatte text mesaj gönder (/api/v2/messages) -> basarili, listede gorunur, socket newMessage gelir.
  2. Yeni chat acarak text gönder (targetUserId ile) -> basarili, chat olusur.
  3. Var olan chatte fotograf + caption gönder -> basarili, type=media, media.url dolu.
  4. 10MB ustu dosya gönder -> 400 döner.
  5. image/gif gönder -> 400 döner.
  6. type='text' + file gönder -> 400 döner.
  7. chatId ve targetUserId olmadan gönder -> 400 döner.

On this page