Allmine API

Bildirim tercihleri — RTK Query

~5 dkMobil / WebKararlı

Notification preferences mobil API slice

Mobil Entegrasyon Dokumani - Bildirim Tercihleri (RTK Query)

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, backend tarafında eklenen kategori bazli bildirim tercihlerini mobil uygulamada RTK Query ile entegre etmek için hazırlandı.

1) Kapsam

Bu doküman su akislari kapsar:

  • Kullanıcı bildirim ayarlarini çekme
  • Kullanıcı bildirim ayarlarini guncelleme
  • Bildirim listesini kategoriye gore filtreleme
  • Bildirimi okundu olarak isaretleme

Bu dokümanın odağı UI tasarımı değil, veri ve API entegrasyonudur.

2) Backend sozlesmesi (ozet)

2.1 User Settings

  • GET /user-settings
  • PATCH /user-settings

Kritik alanlar:

  • notifications: master switch
  • liveStreamNotifications
  • paymentNotifications
  • followNotifications
  • messageNotifications
  • followingActivityNotifications

Not:

  • notifications = false ise tüm kategoriler backend tarafında kapali kabul edilir.
  • Kategori alanlari eski kullanıcılarda eksikse backend true fallback uygular.

2.2 Notifications list

  • GET /api/v2/notifications

Query parametreleri:

  • type?: string
  • category?: 'live_stream' | 'payments' | 'follows' | 'messages' | 'following_activity'
  • read?: boolean
  • isRead?: boolean (legacy)
  • page?: number (varsayilan: 1)
  • limit?: number (varsayilan: 20, maksimum: 100)

2.3 Mark as read

  • PATCH /notifications/:id/read
  • Body: { "isRead": true }

3) Type tanimlari (mobil)

export type NotificationCategory =
  | 'live_stream'
  | 'payments'
  | 'follows'
  | 'messages'
  | 'following_activity';

export type UserSettings = {
  _id: string;
  notifications: boolean;
  totalMinutesVisibility: boolean;
  totalFundVisibility: boolean;
  liveStreamNotifications: boolean;
  paymentNotifications: boolean;
  followNotifications: boolean;
  messageNotifications: boolean;
  followingActivityNotifications: boolean;
  createdAt: string;
  updatedAt: string;
};

export type NotificationItem = {
  id: string;
  userId: string;
  type: string;
  category: NotificationCategory;
  title: string;
  message?: string;
  metadata?: Record<string, unknown>;
  read: boolean;
  createdAt: string;
  updatedAt: string;
};

export type PaginatedResponse<T> = {
  list: T[];
  pagination: {
    currentPage: number;
    totalPages: number;
    totalItems: number;
    itemsPerPage: number;
    hasNextPage: boolean;
    hasPrevPage: boolean;
  };
};

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

4) RTK Query - baseApi varsayimi

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: ['UserSettings', 'Notifications'],
  endpoints: () => ({}),
});

5) RTK Query - settings endpointleri

src/store/api/notificationSettingsApi.ts

import { baseApi } from './baseApi';
import type { BaseResponseDto, UserSettings } from '@/types/notifications';

type UpdateUserSettingsPayload = Partial<
  Pick<
    UserSettings,
    | 'notifications'
    | 'liveStreamNotifications'
    | 'paymentNotifications'
    | 'followNotifications'
    | 'messageNotifications'
    | 'followingActivityNotifications'
  >
>;

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 notificationSettingsApi = baseApi.injectEndpoints({
  endpoints: (builder) => ({
    getUserSettings: builder.query<UserSettings, void>({
      query: () => ({ url: '/user-settings' }),
      transformResponse: (response: BaseResponseDto<UserSettings> | UserSettings) =>
        unwrap(response),
      providesTags: [{ type: 'UserSettings', id: 'ME' }],
    }),

    updateUserSettings: builder.mutation<UserSettings, UpdateUserSettingsPayload>({
      query: (body) => ({
        url: '/user-settings',
        method: 'PATCH',
        body,
      }),
      transformResponse: (response: BaseResponseDto<UserSettings> | UserSettings) =>
        unwrap(response),
      invalidatesTags: [{ type: 'UserSettings', id: 'ME' }],
      async onQueryStarted(arg, { dispatch, queryFulfilled }) {
        const patch = dispatch(
          notificationSettingsApi.util.updateQueryData('getUserSettings', undefined, (draft) => {
            Object.assign(draft, arg);
          }),
        );
        try {
          await queryFulfilled;
        } catch {
          patch.undo();
        }
      },
    }),
  }),
});

export const {
  useGetUserSettingsQuery,
  useUpdateUserSettingsMutation,
} = notificationSettingsApi;

6) RTK Query - notifications endpointleri

src/store/api/notificationsApi.ts

import { baseApi } from './baseApi';
import type { BaseResponseDto, NotificationCategory, NotificationItem } from '@/types/notifications';

type NotificationsQuery = {
  category?: NotificationCategory;
  read?: boolean;
  type?: string;
  page?: number;
  limit?: number;
};

type NotificationsListResponse = {
  list: NotificationItem[];
  pagination: {
    currentPage: number;
    totalPages: number;
    totalItems: number;
    itemsPerPage: number;
    hasNextPage: boolean;
    hasPrevPage: boolean;
  };
};

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 notificationsApi = baseApi.injectEndpoints({
  endpoints: (builder) => ({
    getNotifications: builder.query<NotificationsListResponse, NotificationsQuery | void>({
      query: (params) => ({
        url: '/v2/notifications',
        params,
      }),
      transformResponse: (
        response: BaseResponseDto<NotificationsListResponse> | NotificationsListResponse,
      ) =>
        unwrap(response),
      providesTags: (result) =>
        result
          ? [
              ...result.list.map((n) => ({ type: 'Notifications' as const, id: n.id })),
              { type: 'Notifications' as const, id: 'LIST' },
            ]
          : [{ type: 'Notifications' as const, id: 'LIST' }],
    }),

    markNotificationRead: builder.mutation<NotificationItem, { id: string; isRead: boolean }>({
      query: ({ id, isRead }) => ({
        url: `/notifications/${id}/read`,
        method: 'PATCH',
        body: { isRead },
      }),
      transformResponse: (response: BaseResponseDto<NotificationItem> | NotificationItem) =>
        unwrap(response),
      invalidatesTags: (_result, _err, arg) => [
        { type: 'Notifications', id: arg.id },
        { type: 'Notifications', id: 'LIST' },
      ],
    }),
  }),
});

export const {
  useGetNotificationsQuery,
  useMarkNotificationReadMutation,
} = notificationsApi;

7) UI entegrasyon akisi (onerilen)

7.1 Settings screen

  • Ekran acilisinda useGetUserSettingsQuery() cagir.
  • Master toggle notifications alanina baglanir.
  • Kategori toggle'lari su alanlara baglanir:
    • liveStreamNotifications
    • paymentNotifications
    • followNotifications
    • messageNotifications
    • followingActivityNotifications

Toggle davranisi:

  • Master kapatilirsa sadece notifications: false patch et.
  • Master acilirsa sadece notifications: true patch et.
  • Kategori toggle'da tek alan patch et.
  • Master kapalıyken kategori toggle UI tarafında disabled göster.

Ornek handler:

const [updateSettings, { isLoading: isSaving }] = useUpdateUserSettingsMutation();

const onToggleMaster = async (value: boolean) => {
  await updateSettings({ notifications: value }).unwrap();
};

const onToggleCategory = async (
  key:
    | 'liveStreamNotifications'
    | 'paymentNotifications'
    | 'followNotifications'
    | 'messageNotifications'
    | 'followingActivityNotifications',
  value: boolean,
) => {
  await updateSettings({ [key]: value }).unwrap();
};

7.2 Notification center

  • Tüm liste: useGetNotificationsQuery({ page: 1, limit: 20 })
  • Kategori tablari: useGetNotificationsQuery({ category: 'messages', page: 1, limit: 20 }) gibi
  • Okunmamis liste: useGetNotificationsQuery({ read: false, page: 1, limit: 20 })
  • Listeye ulasim: data?.list, pagination metasi: data?.pagination
  • Kart tiklandiginda markNotificationRead({ id, isRead: true })

8) Push izni ve device token notu

Kategori ayarlari backend tarafında filtreleme yapar. Bu nedenle device token kayit akışınız değişmek zorunda değil.

Onerilen:

  • Uygulama acilisinda izin durumuna gore token varsa POST /notifications/device-token
  • Timezone update yine ayni endpoint ile devam eder

9) Hata durumlari ve UX

  • Patch hatasinda optimistic update geri alınmali
  • Toggle basina loading göstermek için isSaving veya local pending state kullanın
  • Offline durumda queue veya retry stratejisi ekleyin
  • 401 durumda auth yenileme/logout mekanizmasi devreye girmeli

10) QA checklist

  • Master kapali iken kategori toggle UI disabled mi
  • Master acik iken kategori toggle'lar dogru kaydoluyor mu
  • Uygulama yeniden acilinca ayarlar dogru hydrate oluyor mu
  • GET /api/v2/notifications?category=...&page=1&limit=20 dogru liste donduruyor mu
  • Okundu isaretleme sonrasi liste state'i dogru mu
  • Push izni acik/kapali durumunda app crash olmadan ayarlar calisiyor mu

11) Kisa migration notu

Eger mobilde eski local modelde sadece notifications tutuluyorsa:

  • Yeni release ile local modelinize 5 kategori alanini ekleyin
  • Ilk acilista backend GET /user-settings sonucu local state'i overwrite etsin
  • Hardcoded default yerine backend değerini source of truth kabul edin

On this page