159 lines
4.3 KiB
JavaScript
159 lines
4.3 KiB
JavaScript
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);
|
|
}; |