astro_avtourist/frontend/src/lib/pb.ts
Web-serfer b5d2174fdf fix: исправить типизацию, добавить SEO метатеги, исправить API Rules PB
- Добавлены типы PostVotes, VoteStats, Comment, Consultation, PostResponse
- Заменены все any на конкретные типы
- Исправлены catch (error: any) -> catch (error: unknown)
- Добавлены og: и twitter: метатеги в Layout
- Исправлены API Rules в PocketBase (posts, reviews, post_votes)
2026-05-06 22:33:44 +05:00

187 lines
No EOL
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import PocketBase from 'pocketbase';
import type { Post } from '../globalInterfaces';
const PB_URL = import.meta.env.PB_POCKETBASE_URL || 'http://127.0.0.1:8090';
export const pb = new PocketBase(PB_URL);
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 }> {
const votes = await pb.collection('post_votes').getList(1, 1000, {
filter: `post="${postId}"`,
});
const likes = votes.items.filter((v) => v.vote_type === 'like').length;
const dislikes = votes.items.filter((v) => v.vote_type === 'dislike').length;
return { likes, dislikes };
}
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);
}
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,
page: result.page || 1,
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;
}
export const FIXED_BLOG_CATEGORIES = [
'ДТП',
'Лишение прав',
'Страховые споры',
'Штрафы ГИБДД',
'Возмещение ущерба',
'Судебные дела',
];
export async function getAllCategories(): Promise<string[]> {
return ['Все', ...FIXED_BLOG_CATEGORIES];
}
export function getPostImageUrl(post: { image?: string }): string {
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';
}
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;
}