const { ApolloServer } = require('@apollo/server'); const { startServerAndCreateNextHandler } = require('@as-integrations/next'); const { Pool } = require('pg'); // 1. Inisialisasi Pool PostgreSQL untuk Serverless const pool = new Pool({ connectionString: process.env.DATABASE_URL, ssl: { rejectUnauthorized: false, }, max: 1, }); // 2. Definisi Schema GraphQL const typeDefs = `#graphql type Category { id: ID! name: String! description: String products: [Product!]! } type Product { id: ID! title: String! price: Float! category_id: Int created_by: ID category: Category } type Query { categories: [Category!]! category(id: ID!): Category products: [Product!]! product(id: ID!): Product } `; // 3. Plugin Apollo untuk menyuntikkan log counter ke response extensions const metricsPlugin = { async requestDidStart() { return { async willSendResponse({ response, contextValue }) { if (response.body.kind === 'single') { response.body.singleResult.extensions = { ...response.body.singleResult.extensions, nPlusOneMetrics: { totalCalls: contextValue.metrics.categoryProductsCalls, resolverLogs: contextValue.metrics.callLogs, }, }; } }, }; }, }; // 4. Definisi Resolver const resolvers = { Query: { categories: async () => { const result = await pool.query('SELECT * FROM categories ORDER BY id ASC'); return result.rows; }, category: async (_, { id }) => { const result = await pool.query('SELECT * FROM categories WHERE id = $1', [id]); return result.rows[0] || null; }, products: async () => { const result = await pool.query( 'SELECT id, title, price::float, category_id, created_by FROM products ORDER BY id ASC' ); return result.rows; }, product: async (_, { id }) => { const result = await pool.query( 'SELECT id, title, price::float, category_id, created_by FROM products WHERE id = $1', [id] ); return result.rows[0] || null; }, }, // Resolver Relasi Category -> Products (One-to-Many) Category: { products: async (parent, _, contextValue) => { // Increment counter di context request saat ini contextValue.metrics.categoryProductsCalls += 1; const currentCall = contextValue.metrics.categoryProductsCalls; // Simpan log urutan pemanggilan ke array context contextValue.metrics.callLogs.push( `[resolver counter] Category.products dipanggil ${currentCall} kali (category_id=${parent.id})` ); const result = await pool.query( 'SELECT id, title, price::float, category_id, created_by FROM products WHERE category_id = $1 ORDER BY id ASC', [parent.id] ); return result.rows; }, }, // Resolver Relasi Product -> Category (Many-to-One) Product: { category: async (parent) => { if (!parent.category_id) return null; const result = await pool.query('SELECT * FROM categories WHERE id = $1', [parent.category_id]); return result.rows[0] || null; }, }, }; // 5. Inisialisasi Apollo Server dengan Plugin const server = new ApolloServer({ typeDefs, resolvers, introspection: true, csrfPrevention: false, plugins: [metricsPlugin], }); // 6. Inisialisasi Handler dengan Context Per-Request const apolloHandler = startServerAndCreateNextHandler(server, { context: async () => ({ metrics: { categoryProductsCalls: 0, callLogs: [], }, }), }); // 7. Handler CORS dan Serverless Entrypoint module.exports = async function handler(req, res) { const allowedOrigins = new Set([ 'https://studio.apollographql.com', 'https://embeddable-sandbox.cdn.apollographql.com', ]); const origin = req.headers.origin; if (allowedOrigins.has(origin)) { res.setHeader('Access-Control-Allow-Origin', origin); res.setHeader('Vary', 'Origin'); } res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS'); res.setHeader( 'Access-Control-Allow-Headers', 'Content-Type, Authorization, apollo-require-preflight, x-apollo-operation-name' ); if (req.method === 'OPTIONS') { res.status(200).end(); return; } return apolloHandler(req, res); };