Новы глобальные изменения компонентоы
This commit is contained in:
parent
5d7bb04bf1
commit
a269d3459e
43 changed files with 1667 additions and 517 deletions
|
|
@ -28,10 +28,10 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|||
}), { status: 401 });
|
||||
}
|
||||
|
||||
cookies.set('pb_auth', JSON.stringify(pb.authStore.exportToCookie()), {
|
||||
cookies.set('pb_auth', authData.token, {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
sameSite: 'lax',
|
||||
maxAge: 60 * 60 * 24 * 7,
|
||||
});
|
||||
|
|
|
|||
277
frontend/src/pages/api/votes.ts
Normal file
277
frontend/src/pages/api/votes.ts
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
import type { APIRoute } from 'astro';
|
||||
|
||||
const POCKETBASE_URL = import.meta.env.POCKETBASE_URL || 'http://127.0.0.1:8090';
|
||||
|
||||
// Валидация ID PocketBase (15 символов, буквы и цифры)
|
||||
const POCKETBASE_ID_REGEX = /^[a-z0-9]{15}$/;
|
||||
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
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' }}
|
||||
);
|
||||
}
|
||||
|
||||
// Проверяем токен и получаем пользователя через auth-refresh
|
||||
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(
|
||||
JSON.stringify({ error: 'Недействительная сессия' }),
|
||||
{ status: 401, headers: { 'Content-Type': 'application/json' }}
|
||||
);
|
||||
}
|
||||
|
||||
const authData = await authResponse.json();
|
||||
|
||||
// ВАЖНО: Явно берем id, а не email
|
||||
const userId = authData.record?.id;
|
||||
const userEmail = authData.record?.email;
|
||||
|
||||
console.log('[Vote API] Auth data:', { userId, userEmail, record: authData.record });
|
||||
|
||||
// Защита: проверяем что это действительно ID, а не email
|
||||
if (!userId || EMAIL_REGEX.test(userId)) {
|
||||
console.error('[Vote API] Invalid userId (looks like email):', userId);
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Ошибка идентификации пользователя' }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' }}
|
||||
);
|
||||
}
|
||||
|
||||
if (!POCKETBASE_ID_REGEX.test(userId)) {
|
||||
console.error('[Vote API] Invalid userId format:', userId);
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Некорректный ID пользователя' }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' }}
|
||||
);
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { post_id, vote_type } = body;
|
||||
|
||||
if (!post_id || !POCKETBASE_ID_REGEX.test(post_id)) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Некорректный post_id' }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' }}
|
||||
);
|
||||
}
|
||||
|
||||
if (!vote_type || !['like', 'dislike'].includes(vote_type)) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Некорректный vote_type' }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' }}
|
||||
);
|
||||
}
|
||||
|
||||
// Проверяем существующий голос
|
||||
const existingVoteRes = await fetch(
|
||||
`${POCKETBASE_URL}/api/collections/post_votes/records?` +
|
||||
new URLSearchParams({
|
||||
filter: `post="${post_id}" && user="${userId}"`,
|
||||
}),
|
||||
{
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
}
|
||||
);
|
||||
|
||||
let userVote: 'like' | 'dislike' | null = null;
|
||||
let method = 'POST';
|
||||
let url = `${POCKETBASE_URL}/api/collections/post_votes/records`;
|
||||
let voteId = null;
|
||||
|
||||
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) {
|
||||
// Удаляем голос (toggle off)
|
||||
method = 'DELETE';
|
||||
url = `${POCKETBASE_URL}/api/collections/post_votes/records/${voteId}`;
|
||||
} else {
|
||||
// Обновляем
|
||||
method = 'PATCH';
|
||||
url = `${POCKETBASE_URL}/api/collections/post_votes/records/${voteId}`;
|
||||
userVote = vote_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Выполняем операцию с голосом
|
||||
const voteBody = method === 'POST' ? JSON.stringify({
|
||||
post: post_id,
|
||||
user: userId, // Теперь точно ID, а не email
|
||||
vote_type,
|
||||
}) : method === 'PATCH' ? JSON.stringify({ vote_type }) : null;
|
||||
|
||||
const voteRes = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: voteBody,
|
||||
});
|
||||
|
||||
if (!voteRes.ok && method !== 'DELETE') {
|
||||
const errorText = await voteRes.text();
|
||||
console.error('[Vote API] Failed to save vote:', errorText);
|
||||
throw new Error('Failed to save vote');
|
||||
}
|
||||
|
||||
if (method === 'POST') userVote = vote_type;
|
||||
if (method === 'DELETE') userVote = null;
|
||||
|
||||
// Получаем актуальные счетчики
|
||||
const votesRes = await fetch(
|
||||
`${POCKETBASE_URL}/api/collections/post_votes/records?` +
|
||||
new URLSearchParams({
|
||||
filter: `post="${post_id}"`,
|
||||
fields: 'vote_type',
|
||||
}),
|
||||
{
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
}
|
||||
);
|
||||
|
||||
let likes = 0;
|
||||
let dislikes = 0;
|
||||
|
||||
if (votesRes.ok) {
|
||||
const votesData = await votesRes.json();
|
||||
likes = votesData.items.filter((v: any) => v.vote_type === 'like').length;
|
||||
dislikes = votesData.items.filter((v: any) => v.vote_type === 'dislike').length;
|
||||
}
|
||||
|
||||
// Обновляем счетчики в посте
|
||||
await fetch(
|
||||
`${POCKETBASE_URL}/api/collections/posts/records/${post_id}`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ likes, dislikes }),
|
||||
}
|
||||
);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ likes, dislikes, userVote }),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' }}
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Vote API] Error:', error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Внутренняя ошибка сервера' }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' }}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const GET: APIRoute = async ({ url, cookies }) => {
|
||||
try {
|
||||
const postId = url.searchParams.get('post_id');
|
||||
const token = cookies.get('pb_auth')?.value;
|
||||
|
||||
if (!postId || !POCKETBASE_ID_REGEX.test(postId)) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Некорректный post_id' }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' }}
|
||||
);
|
||||
}
|
||||
|
||||
// Получаем пост со счетчиками
|
||||
const postRes = await fetch(
|
||||
`${POCKETBASE_URL}/api/collections/posts/records/${postId}`,
|
||||
{
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
||||
}
|
||||
);
|
||||
|
||||
if (!postRes.ok) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Пост не найден' }),
|
||||
{ status: 404, headers: { 'Content-Type': 'application/json' }}
|
||||
);
|
||||
}
|
||||
|
||||
const post = await postRes.json();
|
||||
|
||||
// Определяем голос текущего пользователя
|
||||
let userVote: 'like' | 'dislike' | null = null;
|
||||
|
||||
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;
|
||||
|
||||
if (userId && POCKETBASE_ID_REGEX.test(userId)) {
|
||||
const userVoteRes = await fetch(
|
||||
`${POCKETBASE_URL}/api/collections/post_votes/records?` +
|
||||
new URLSearchParams({
|
||||
filter: `post="${postId}" && user="${userId}"`,
|
||||
}),
|
||||
{
|
||||
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) {
|
||||
console.error('[Vote API] Error fetching user vote:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
likes: post.likes || 0,
|
||||
dislikes: post.dislikes || 0,
|
||||
userVote,
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' }}
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Vote API GET] Error:', error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Внутренняя ошибка сервера' }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' }}
|
||||
);
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue