2026-04-20 20:27:04 +05:00
|
|
|
|
import type { APIRoute } from 'astro';
|
|
|
|
|
|
|
|
|
|
|
|
const POCKETBASE_URL = import.meta.env.POCKETBASE_URL || 'http://127.0.0.1:8090';
|
|
|
|
|
|
|
2026-04-21 20:04:13 +05:00
|
|
|
|
const PB_ID_REGEX = /^[a-z0-9]{15}$/;
|
2026-04-20 20:27:04 +05:00
|
|
|
|
|
|
|
|
|
|
export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const token = cookies.get('pb_auth')?.value;
|
|
|
|
|
|
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({ error: 'Требуется авторизация' }),
|
|
|
|
|
|
{ status: 401, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const authResponse = await fetch(
|
|
|
|
|
|
`${POCKETBASE_URL}/api/collections/users/auth-refresh`,
|
|
|
|
|
|
{
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Authorization': `Bearer ${token}`,
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (!authResponse.ok) {
|
|
|
|
|
|
return new Response(
|
2026-04-21 20:04:13 +05:00
|
|
|
|
JSON.stringify({ error: 'Нед<D0B5><D0B4>йствительная сессия' }),
|
2026-04-20 20:27:04 +05:00
|
|
|
|
{ status: 401, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const authData = await authResponse.json();
|
|
|
|
|
|
const userId = authData.record?.id;
|
|
|
|
|
|
|
2026-04-21 20:04:13 +05:00
|
|
|
|
if (!userId || !PB_ID_REGEX.test(userId)) {
|
2026-04-20 20:27:04 +05:00
|
|
|
|
return new Response(
|
2026-04-21 20:04:13 +05:00
|
|
|
|
JSON.stringify({ error: 'Ошибка идентификации' }),
|
2026-04-20 20:27:04 +05:00
|
|
|
|
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const body = await request.json();
|
|
|
|
|
|
const { review_id, vote_type } = body;
|
|
|
|
|
|
|
2026-04-21 20:04:13 +05:00
|
|
|
|
if (!review_id || !PB_ID_REGEX.test(review_id)) {
|
2026-04-20 20:27:04 +05:00
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({ error: 'Некорректный review_id' }),
|
|
|
|
|
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-21 20:04:13 +05:00
|
|
|
|
if (!vote_type || !['likes', 'dislikes'].includes(vote_type)) {
|
2026-04-20 20:27:04 +05:00
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({ error: 'Некорректный vote_type' }),
|
|
|
|
|
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const existingVoteRes = await fetch(
|
2026-04-21 20:04:13 +05:00
|
|
|
|
`${POCKETBASE_URL}/api/collections/review_votes/records?filter=(review="${review_id}")&&(user="${userId}")`,
|
2026-04-20 20:27:04 +05:00
|
|
|
|
{
|
|
|
|
|
|
headers: { 'Authorization': `Bearer ${token}` },
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-04-21 20:04:13 +05:00
|
|
|
|
let userVote: 'likes' | 'dislikes' | null = null;
|
2026-04-20 20:27:04 +05:00
|
|
|
|
let method = 'POST';
|
|
|
|
|
|
let url = `${POCKETBASE_URL}/api/collections/review_votes/records`;
|
2026-04-21 20:04:13 +05:00
|
|
|
|
let voteId: string | null = null;
|
2026-04-20 20:27:04 +05:00
|
|
|
|
|
|
|
|
|
|
if (existingVoteRes.ok) {
|
|
|
|
|
|
const existingData = await existingVoteRes.json();
|
|
|
|
|
|
if (existingData.items?.length > 0) {
|
|
|
|
|
|
const existing = existingData.items[0];
|
|
|
|
|
|
voteId = existing.id;
|
|
|
|
|
|
|
|
|
|
|
|
if (existing.vote_type === vote_type) {
|
|
|
|
|
|
method = 'DELETE';
|
|
|
|
|
|
url = `${POCKETBASE_URL}/api/collections/review_votes/records/${voteId}`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
method = 'PATCH';
|
|
|
|
|
|
url = `${POCKETBASE_URL}/api/collections/review_votes/records/${voteId}`;
|
|
|
|
|
|
userVote = vote_type;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const voteBody = method === 'POST'
|
2026-04-21 20:04:13 +05:00
|
|
|
|
? JSON.stringify({ review: review_id, user: userId, vote_type: vote_type })
|
2026-04-20 20:27:04 +05:00
|
|
|
|
: method === 'PATCH'
|
2026-04-21 20:04:13 +05:00
|
|
|
|
? JSON.stringify({ vote_type: vote_type })
|
|
|
|
|
|
: undefined;
|
2026-04-20 20:27:04 +05:00
|
|
|
|
|
|
|
|
|
|
const voteRes = await fetch(url, {
|
|
|
|
|
|
method,
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Authorization': `Bearer ${token}`,
|
2026-04-21 20:04:13 +05:00
|
|
|
|
'Content-Type': method !== 'DELETE' ? 'application/json' : undefined,
|
2026-04-20 20:27:04 +05:00
|
|
|
|
},
|
|
|
|
|
|
body: voteBody,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!voteRes.ok && method !== 'DELETE') {
|
|
|
|
|
|
const errorText = await voteRes.text();
|
2026-04-21 20:04:13 +05:00
|
|
|
|
console.error('[ReviewVote API] Vote error:', errorText);
|
2026-04-20 20:27:04 +05:00
|
|
|
|
throw new Error('Failed to save vote');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (method === 'POST') userVote = vote_type;
|
|
|
|
|
|
if (method === 'DELETE') userVote = null;
|
|
|
|
|
|
|
2026-04-21 20:04:13 +05:00
|
|
|
|
const votesRes = await fetch(
|
|
|
|
|
|
`${POCKETBASE_URL}/api/collections/review_votes/records?filter=(review="${review_id}")`,
|
|
|
|
|
|
{}
|
2026-04-20 20:27:04 +05:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let likes = 0;
|
|
|
|
|
|
let dislikes = 0;
|
|
|
|
|
|
|
2026-04-21 20:04:13 +05:00
|
|
|
|
if (votesRes.ok) {
|
|
|
|
|
|
const votesData = await votesRes.json();
|
|
|
|
|
|
likes = votesData.items?.filter((v: any) => v.vote_type === 'likes').length || 0;
|
|
|
|
|
|
dislikes = votesData.items?.filter((v: any) => v.vote_type === 'dislikes').length || 0;
|
2026-04-20 20:27:04 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({ likes, dislikes, userVote }),
|
|
|
|
|
|
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[ReviewVote API] Error:', error);
|
|
|
|
|
|
return new Response(
|
2026-04-21 20:04:13 +05:00
|
|
|
|
JSON.stringify({ error: 'Внутренняя ошибка' }),
|
2026-04-20 20:27:04 +05:00
|
|
|
|
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const GET: APIRoute = async ({ url, cookies }) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const reviewId = url.searchParams.get('review_id');
|
|
|
|
|
|
const token = cookies.get('pb_auth')?.value;
|
|
|
|
|
|
|
2026-04-21 20:04:13 +05:00
|
|
|
|
if (!reviewId || !PB_ID_REGEX.test(reviewId)) {
|
2026-04-20 20:27:04 +05:00
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({ error: 'Некорректный review_id' }),
|
|
|
|
|
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const votesRes = await fetch(
|
2026-04-21 20:04:13 +05:00
|
|
|
|
`${POCKETBASE_URL}/api/collections/review_votes/records?filter=(review="${reviewId}")`,
|
|
|
|
|
|
{}
|
2026-04-20 20:27:04 +05:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let likes = 0;
|
|
|
|
|
|
let dislikes = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (votesRes.ok) {
|
|
|
|
|
|
const votesData = await votesRes.json();
|
2026-04-21 20:04:13 +05:00
|
|
|
|
likes = votesData.items?.filter((v: any) => v.vote_type === 'likes').length || 0;
|
|
|
|
|
|
dislikes = votesData.items?.filter((v: any) => v.vote_type === 'dislikes').length || 0;
|
2026-04-20 20:27:04 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-21 20:04:13 +05:00
|
|
|
|
let userVote: 'likes' | 'dislikes' | null = null;
|
2026-04-20 20:27:04 +05:00
|
|
|
|
|
|
|
|
|
|
if (token) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const authRes = await fetch(
|
|
|
|
|
|
`${POCKETBASE_URL}/api/collections/users/auth-refresh`,
|
|
|
|
|
|
{
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'Authorization': `Bearer ${token}` },
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (authRes.ok) {
|
|
|
|
|
|
const authData = await authRes.json();
|
|
|
|
|
|
const userId = authData.record?.id;
|
|
|
|
|
|
|
2026-04-21 20:04:13 +05:00
|
|
|
|
if (userId && PB_ID_REGEX.test(userId)) {
|
2026-04-20 20:27:04 +05:00
|
|
|
|
const userVoteRes = await fetch(
|
2026-04-21 20:04:13 +05:00
|
|
|
|
`${POCKETBASE_URL}/api/collections/review_votes/records?filter=(review="${reviewId}")&&(user="${userId}")`,
|
2026-04-20 20:27:04 +05:00
|
|
|
|
{
|
|
|
|
|
|
headers: { 'Authorization': `Bearer ${token}` },
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (userVoteRes.ok) {
|
|
|
|
|
|
const userVoteData = await userVoteRes.json();
|
|
|
|
|
|
if (userVoteData.items?.length > 0) {
|
|
|
|
|
|
userVote = userVoteData.items[0].vote_type;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2026-04-21 20:04:13 +05:00
|
|
|
|
console.error('[ReviewVote API GET] Error:', e);
|
2026-04-20 20:27:04 +05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({ likes, dislikes, userVote }),
|
|
|
|
|
|
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[ReviewVote API GET] Error:', error);
|
|
|
|
|
|
return new Response(
|
2026-04-21 20:04:13 +05:00
|
|
|
|
JSON.stringify({ error: 'Внутренняя ошибка' }),
|
2026-04-20 20:27:04 +05:00
|
|
|
|
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|