250715
This commit is contained in:
112
functions/src/board/fetchSingleItem.ts
Normal file
112
functions/src/board/fetchSingleItem.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { onRequest } from 'firebase-functions/v2/https';
|
||||
import { logger } from 'firebase-functions/v2';
|
||||
import { getFirestore } from 'firebase-admin/firestore';
|
||||
import { initializeApp, getApps } from 'firebase-admin/app';
|
||||
import type { BoardItem, BoardAccessMode } from '../types/boardItem';
|
||||
|
||||
import { corsMiddlewareHandler, ALLOWED_COLLECTIONS } from '../config';
|
||||
import {
|
||||
isValidBoardItem,
|
||||
verifySessionFromRequest,
|
||||
buildQueryWithAccessControl,
|
||||
} from '../utils';
|
||||
|
||||
if (!getApps().length) {
|
||||
initializeApp();
|
||||
}
|
||||
|
||||
const db = getFirestore();
|
||||
|
||||
export const fetchSingleItem = onRequest(
|
||||
{ region: 'asia-northeast3' },
|
||||
async (request, response) => {
|
||||
return corsMiddlewareHandler(request, response, async () => {
|
||||
logger.info('fetchSingleItem: CORS-enabled function called');
|
||||
|
||||
/* 1.Method guard ------------------------------------------------------------------ */
|
||||
if (request.method !== 'GET') {
|
||||
response.status(405).send({ message: 'Method Not Allowed' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Auth (optional)
|
||||
let userRole = 0;
|
||||
let userId: string | null = null;
|
||||
|
||||
const authUser = await verifySessionFromRequest(request);
|
||||
if (authUser) {
|
||||
userId = authUser.uid;
|
||||
userRole = authUser.role;
|
||||
}
|
||||
|
||||
// 3. Query params
|
||||
const {
|
||||
collection = '',
|
||||
docId = '',
|
||||
access = 'public',
|
||||
} = request.query as Record<string, string>;
|
||||
|
||||
if (!collection || !docId) {
|
||||
response.status(400).send({ message: 'Missing parameters' });
|
||||
return;
|
||||
}
|
||||
|
||||
// ❶collection allow‑list
|
||||
if (
|
||||
!ALLOWED_COLLECTIONS.has(
|
||||
collection as unknown as typeof ALLOWED_COLLECTIONS extends Set<
|
||||
infer U
|
||||
>
|
||||
? U
|
||||
: never,
|
||||
)
|
||||
) {
|
||||
response.status(400).send({ message: 'Invalid collection' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!['public', 'private', 'admin'].includes(access)) {
|
||||
response.status(400).send({ message: 'Bad access mode' });
|
||||
return;
|
||||
}
|
||||
const accessParam = (access ?? 'public') as BoardAccessMode;
|
||||
const effectiveAccess = userId ? accessParam : 'public'; // force public if anonymous
|
||||
|
||||
try {
|
||||
// Build query with access control
|
||||
const query = buildQueryWithAccessControl(
|
||||
db.collection(collection),
|
||||
effectiveAccess,
|
||||
userId,
|
||||
userRole,
|
||||
).where('__name__', '==', docId);
|
||||
|
||||
const snap = await query.limit(1).get();
|
||||
|
||||
if (snap.empty) {
|
||||
response
|
||||
.status(404)
|
||||
.send({ message: 'Item not found or access denied' });
|
||||
return;
|
||||
}
|
||||
|
||||
const docSnap = snap.docs[0];
|
||||
const data = docSnap.data() as BoardItem;
|
||||
if (!isValidBoardItem(data)) {
|
||||
response.status(500).send({ message: 'Invalid item data' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (access === 'public') {
|
||||
response.set('Cache-Control', 'public,max-age=60');
|
||||
}
|
||||
|
||||
response.status(200).send({ ...data, docId: docSnap.id });
|
||||
return;
|
||||
} catch (err) {
|
||||
logger.error('fetchSingleItem: Firestore error', err);
|
||||
response.status(500).send({ message: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user