Saltar al contenido principal

GraphQL y Apollo

La API de Closegram es 100% GraphQL. El backend usa Apollo Server con Express. El frontend usa Apollo Client con soporte para HTTP, WebSocket y carga de archivos.

Endpoints

El backend expone dos endpoints de GraphQL separados, cada uno respaldado por su propia instancia de ApolloServer y su propio esquema (apps/backend/api/server.js):

EndpointConsumidorEsquema
POST /web/graphqlfrontend-nextjs, app iOSEsquema de cliente — todos los campos raíz Query/Mutation excepto los que tienen el prefijo admin*
POST /admin/graphqlfrontend-adminEsquema de administración — solo los campos raíz Query/Mutation con el prefijo admin* (por ejemplo, adminLogin, adminGetUsers, adminGetReports)

La ruta legacy POST /graphql fue eliminada. El cliente (web + iOS) ahora usa /web/graphql, espejando /admin/graphql del panel. Configura NEXT_PUBLIC_GRAPHQL_URL / NEXT_PUBLIC_WS_URL a /web/graphql para la web; iOS usa /web/graphql en APIConfig.swift.

Ambos esquemas se derivan al iniciar a partir de un esquema combinado mediante mapSchema/MapperKind, filtrando los campos raíz según si su nombre coincide con /^admin[A-Z]/. Esto es una división real de esquema, no solo de enrutamiento: las operaciones admin* están estructuralmente ausentes de /web/graphql (no aparecen en la introspección ahí), y las operaciones regulares de cliente están ausentes de /admin/graphql.

Las suscripciones por WebSocket (graphql-ws) están montadas en /web/graphql — no existen suscripciones para el panel de administración.

Ambos endpoints comparten el mismo constructor de contexto (graphql/context/index.js), que resuelve context.user (usuario regular autenticado con Firebase) y context.admin (JWT de AdminUser, firmado con un secreto separado) de forma independiente en cada solicitud — una solicitud solo llega a completar el que coincide con el token Bearer que recibió.

Resolvers del backend

ResolverDescripción
user.resolver.jsRegistro, inicio de sesión, seguir, privacidad, sesiones
post.resolver.jsCRUD de publicaciones, feed, seguimiento de vistas
post-interaction.resolver.jsReacciones (6 tipos)
post-comment.resolver.jsComentarios anidados
message.resolver.jsMensajes enriquecidos, reacciones, encuestas, ubicación
conversation.resolver.jsConversaciones y participantes
call.resolver.jsLlamadas de voz/video, modo espectador
notification.resolver.jsNotificaciones y configuración
coin-package.resolver.jsPaquetes de monedas (administración)
coin-transaction.resolver.jsSaldo e historial
coin-tip.resolver.jsPropinas en contenido
coin-purchase.resolver.jsCompras de paquetes de monedas
payment-transaction.resolver.jsHistorial de pagos de Stripe
payment-methods.resolvers.jsMétodos de pago guardados
subscription-tier.resolver.jsNiveles de suscripción de creadores
user-subscription.resolver.jsSuscripciones activas
message-purchase.resolver.jsMensajes pagos (bloqueo/desbloqueo)
saved-post.resolver.jsPublicaciones guardadas
saved-collection.resolver.jsColecciones de publicaciones
hashtag.resolver.jsHashtags y tendencias
content-report.resolver.jsReportes de contenido
content-moderation.resolver.jsModeración con Google Cloud AI
verification.resolver.jsInsignias de verificación
conversation-subscription.resolver.jsSuscripciones de GraphQL de conversaciones
admin-user.resolver.jsGestión de usuarios (administración)
admin-dashboard.resolver.jsDashboard de administración
user-moderation.resolver.jsRestricciones y moderación de usuarios

Tipos del esquema

El esquema está dividido en archivos por dominio bajo graphql/types/ y combinado en graphql/typeDefs.js.

Escalares personalizados: DateTime, JSON, Upload.

Apollo Client (frontend)

Paquetes GraphQL y generación de código

El esquema y todas las operaciones viven en un único paquete fuente de verdad, y tres paquetes de codegen generan clientes tipados a partir de él. Web y admin nunca comparten una operación.

packages/
graphql/ # @closegram/graphql — ÚNICA FUENTE DE VERDAD
schema/
schema.web.graphqls # esquema cliente (web + iOS), descargado de /web/graphql
schema.admin.graphqls # esquema admin, descargado de /admin/graphql
operations/
Web/** # operaciones de cliente (frontend-nextjs + iOS)
Admin/** # operaciones de admin (solo frontend-admin)
apollo-web/ # @closegram/apollo-web — tipos TS + hooks para frontend-nextjs
apollo-admin/ # @closegram/apollo-admin — tipos TS + hooks para frontend-admin
apollo-swift/ # paquete Swift (producto/módulo: ClosegramGraphQL) para apps/ios
  • apollo-web lee schema.web.graphqls + operations/Web/** → cliente TS que frontend-nextjs importa de @closegram/apollo-web.
  • apollo-admin lee ambos esquemas (fusionados) + operations/Admin/** → cliente TS que frontend-admin importa de @closegram/apollo-admin.
  • apollo-swift lee schema.web.graphqls + operations/Web/** → el módulo Swift ClosegramGraphQL que importa la app iOS. Su paquete SwiftPM se llama apollo-swift (no apollo-ios) para evitar el choque de nombre con la dependencia apollo-ios de Apollo; el target ejecutable de codegen es codegen.

Regenera todo desde la raíz del repo:

npm run codegen
# = codegen:schema (descarga AMBOS esquemas del backend en ejecución por introspección)
# -> codegen:web -> codegen:admin -> codegen:ios

codegen:schema hace introspección al backend en ejecución, así que debe estar arriba (pega a /web/graphql y /admin/graphql). Para regenerar solo tipos desde los snapshots del esquema, corre codegen:web / codegen:admin / codegen:ios por separado. El de iOS es cd packages/apollo-swift && swift run codegen.

Regla: las operaciones admin viven solo en operations/Admin y las de web solo en operations/Web — los dos frontends nunca comparten una query. Todo campo exclusivo de admin lleva prefijo admin* en el backend para caer estructuralmente en el esquema/endpoint de admin.

Apollo Client (frontend)

Configurado en packages/apollo-web (@closegram/apollo-web):

// Link chain
authLink // attaches Firebase token in Authorization header
httpLink // HTTP queries and mutations
wsLink // WebSocket subscriptions
uploadLink // multipart for file uploads
splitLink // routes subscriptions → wsLink, rest → httpLink/uploadLink
const authLink = setContext(async (_, { headers }) => {
const token = await getFirebaseToken(); // auto-refreshes before expiry
return { headers: { ...headers, authorization: token ? `Bearer ${token}` : "" } };
});

Caché con paginación

new InMemoryCache({
typePolicies: {
Query: {
fields: {
feed: relayStylePagination(),
myTransactions: relayStylePagination(),
myNotifications: relayStylePagination(),
conversationMessages: { keyArgs: ["conversationId"] },
},
},
},
})

Contexto del servidor

El contexto de cada solicitud (graphql/context/index.js) resuelve:

  • El usuario autenticado a partir del token Bearer (Firebase Admin SDK)
  • El administrador autenticado (para resolvers exclusivos de administración) — ver Endpoints arriba para saber cómo se separa /admin/graphql de /web/graphql
  • El contexto de WebSocket para las suscripciones (ws-context.js)

Suscripciones activas

EventoDescripción
messageAddedMensaje nuevo en la conversación
messageUpdatedMensaje editado
messageDeletedMensaje eliminado
messageReactionAddedNueva reacción en un mensaje
typingIndicatorCambio en el estado de escritura
messageReadConfirmación de lectura
conversationAddedConversación nueva
conversationUpdatedConversación actualizada
callIncomingLlamada entrante
callStatusChangedCambio de estado de la llamada
callEndedLlamada finalizada
participantJoined / participantLeftCambio de participante en la llamada
speakerRequestReceivedSolicitud para hablar en una llamada grupal
speakerRequestResponseRespuesta a la solicitud para hablar