2026-04-15 02:12:25 +05:00
|
|
|
|
import PocketBase from 'pocketbase';
|
2026-04-15 21:32:55 +05:00
|
|
|
|
import type { Post } from '../globalInterfaces';
|
2026-04-15 02:12:25 +05:00
|
|
|
|
|
2026-05-04 03:10:06 +05:00
|
|
|
|
const PB_URL = import.meta.env.PB_POCKETBASE_URL || 'http://127.0.0.1:8090';
|
2026-04-15 02:12:25 +05:00
|
|
|
|
|
|
|
|
|
|
export const pb = new PocketBase(PB_URL);
|
|
|
|
|
|
|
2026-04-17 17:35:17 +05:00
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
|
|
const token = localStorage.getItem('auth_token');
|
|
|
|
|
|
const userStr = localStorage.getItem('user');
|
|
|
|
|
|
|
|
|
|
|
|
// Инициализируем куку из localStorage если её нет
|
|
|
|
|
|
if (token && !document.cookie.includes('pb_auth')) {
|
|
|
|
|
|
document.cookie = `pb_auth=${token}; path=/; max-age=${7 * 24 * 60 * 60}; SameSite=Lax`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (token && userStr) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const user = JSON.parse(userStr);
|
|
|
|
|
|
pb.authStore.save(token, user);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('Failed to restore auth:', e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PostVotes {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
post_id: string;
|
|
|
|
|
|
user_id: string;
|
|
|
|
|
|
vote_type: 'like' | 'dislike';
|
|
|
|
|
|
created: string;
|
|
|
|
|
|
updated: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface VoteStats {
|
|
|
|
|
|
likes: number;
|
|
|
|
|
|
dislikes: number;
|
|
|
|
|
|
userVote: 'like' | 'dislike' | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function getPostVotes(postId: string): Promise<VoteStats> {
|
|
|
|
|
|
const votes = await pb.collection('post_votes').getList(1, 1000, {
|
|
|
|
|
|
filter: `post_id="${postId}"`,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const likes = votes.items.filter((v) => v.vote_type === 'like').length;
|
|
|
|
|
|
const dislikes = votes.items.filter((v) => v.vote_type === 'dislike').length;
|
|
|
|
|
|
|
|
|
|
|
|
let userVote: 'like' | 'dislike' | null = null;
|
|
|
|
|
|
if (pb.authStore.isValid) {
|
|
|
|
|
|
const userId = pb.authStore.model?.id;
|
|
|
|
|
|
const userVoteRecord = votes.items.find((v) => v.user_id === userId);
|
|
|
|
|
|
if (userVoteRecord) {
|
|
|
|
|
|
userVote = userVoteRecord.vote_type as 'like' | 'dislike';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { likes, dislikes, userVote };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function getPostVotesStats(postId: string): Promise<{ likes: number; dislikes: number }> {
|
2026-04-18 18:25:10 +05:00
|
|
|
|
// Получаем данные из коллекции голосов
|
|
|
|
|
|
const votes = await pb.collection('post_votes').getList(1, 1000, {
|
|
|
|
|
|
filter: `post="${postId}"`,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const likes = votes.items.filter((v: any) => v.vote_type === 'like').length;
|
|
|
|
|
|
const dislikes = votes.items.filter((v: any) => v.vote_type === 'dislike').length;
|
2026-04-17 17:35:17 +05:00
|
|
|
|
|
2026-04-18 18:25:10 +05:00
|
|
|
|
return { likes, dislikes };
|
2026-04-17 17:35:17 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function vote(postId: string, voteType: 'like' | 'dislike'): Promise<VoteStats> {
|
|
|
|
|
|
const userId = pb.authStore.model?.id;
|
|
|
|
|
|
|
|
|
|
|
|
if (!userId) {
|
|
|
|
|
|
throw new Error('Требуется авторизация');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const existingVotes = await pb.collection('post_votes').getList(1, 1, {
|
|
|
|
|
|
filter: `post_id="${postId}" && user_id="${userId}"`,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (existingVotes.items.length > 0) {
|
|
|
|
|
|
const existingVote = existingVotes.items[0] as unknown as PostVotes;
|
|
|
|
|
|
|
|
|
|
|
|
if (existingVote.vote_type === voteType) {
|
|
|
|
|
|
await pb.collection('post_votes').delete(existingVote.id);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await pb.collection('post_votes').update(existingVote.id, {
|
|
|
|
|
|
vote_type: voteType,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await pb.collection('post_votes').create({
|
|
|
|
|
|
post_id: postId,
|
|
|
|
|
|
user_id: userId,
|
|
|
|
|
|
vote_type: voteType,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return getPostVotes(postId);
|
|
|
|
|
|
}
|
2026-04-15 12:36:58 +05:00
|
|
|
|
|
2026-04-15 02:12:25 +05:00
|
|
|
|
export async function getPosts(options?: {
|
|
|
|
|
|
page?: number;
|
|
|
|
|
|
perPage?: number;
|
|
|
|
|
|
category?: string;
|
|
|
|
|
|
search?: string;
|
|
|
|
|
|
}): Promise<{ posts: Post[]; total: number; page: number; totalPages: number }> {
|
|
|
|
|
|
const page = options?.page || 1;
|
|
|
|
|
|
const perPage = options?.perPage || 10;
|
|
|
|
|
|
|
|
|
|
|
|
const filter: string[] = ['draft = false'];
|
|
|
|
|
|
|
|
|
|
|
|
if (options?.category && options.category !== 'Все') {
|
|
|
|
|
|
filter.push(`category = "${options.category}"`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (options?.search) {
|
|
|
|
|
|
filter.push(`(title ~ "${options.search}" || description ~ "${options.search}")`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const result = await pb.collection('posts').getList(page, perPage, {
|
|
|
|
|
|
filter: filter.join(' && '),
|
|
|
|
|
|
sort: '-date',
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
posts: (result.items || []) as unknown as Post[],
|
|
|
|
|
|
total: result.totalItems || 0,
|
2026-04-15 19:32:21 +05:00
|
|
|
|
page: result.page || 1,
|
2026-04-15 02:12:25 +05:00
|
|
|
|
totalPages: result.totalPages || 1,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function getPostBySlug(slug: string): Promise<Post | null> {
|
|
|
|
|
|
const result = await pb.collection('posts').getList(1, 1, {
|
|
|
|
|
|
filter: `slug="${slug}" && draft = false`,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!result.items || result.totalItems === 0) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result.items[0] as unknown as Post;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-26 22:45:19 +05:00
|
|
|
|
export const FIXED_BLOG_CATEGORIES = [
|
|
|
|
|
|
'ДТП',
|
|
|
|
|
|
'Лишение прав',
|
|
|
|
|
|
'Страховые споры',
|
|
|
|
|
|
'Штрафы ГИБДД',
|
|
|
|
|
|
'Возмещение ущерба',
|
|
|
|
|
|
'Судебные дела',
|
|
|
|
|
|
];
|
2026-04-15 02:12:25 +05:00
|
|
|
|
|
2026-04-26 22:45:19 +05:00
|
|
|
|
export async function getAllCategories(): Promise<string[]> {
|
|
|
|
|
|
return ['Все', ...FIXED_BLOG_CATEGORIES];
|
2026-04-15 12:36:58 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-15 21:32:55 +05:00
|
|
|
|
export function getPostImageUrl(post: { image?: string }): string {
|
2026-04-15 12:36:58 +05:00
|
|
|
|
if (post.image) {
|
|
|
|
|
|
const fileUrl = pb.files.getUrl(post, post.image);
|
|
|
|
|
|
if (fileUrl.startsWith('/')) {
|
|
|
|
|
|
return `${PB_URL}${fileUrl}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
return fileUrl;
|
|
|
|
|
|
}
|
|
|
|
|
|
return '/images/blog/default.avif';
|
2026-04-26 23:47:23 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function getPostViews(postId: string): Promise<number> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const post = await pb.collection('posts').getOne(postId);
|
|
|
|
|
|
return post.views || 0;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function incrementPostViews(postId: string): Promise<number> {
|
|
|
|
|
|
const post = await pb.collection('posts').getOne(postId);
|
|
|
|
|
|
const newViews = (post.views || 0) + 1;
|
|
|
|
|
|
await pb.collection('posts').update(postId, { views: newViews });
|
|
|
|
|
|
return newViews;
|
2026-04-15 02:12:25 +05:00
|
|
|
|
}
|