diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..242cf44
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,57 @@
+# Правила взаимодействия с Агентом
+
+## Основные принципы
+
+1. **Изменения в коде возможны только с явного разрешения пользователя**
+ - Перед внесением любых изменений в файлы ассистент должен получить подтверждение от пользователя
+ - Все изменения должны быть предварительно объяснены пользователю
+ - Перед решением конкретной задачи всегда составлять план
+ - После внесения изменений в код - проводить проверку - только после этого приступать к дальнейшему решению задачи
+
+2. **Прозрачность действий**
+ - Ассистент должен объяснить, какие изменения планируется внести
+ - Необходимо указать, в какие файлы будут внесены изменения
+ - Следует объяснить последствия предполагаемых изменений
+
+3. **Безопасность кода**
+ - Все изменения должны проходить проверку на безопасность
+ - Не должны вноситься изменения, которые могут повредить функциональность приложения
+ - Рекомендуется создание резервных копий при значительных изменениях
+
+4. **Согласование архитектурных решений**
+ - При внесении изменений, затрагивающих архитектуру приложения, необходима дискуссия с пользователем
+ - Предложения по улучшению архитектуры должны обсуждаться до реализации
+
+5. **Работа с разными типами проектов**
+ - Уважать существующую архитектуру и стиль кода проекта
+ - Следовать установленным в проекте принципам и паттернам
+
+6. **Использование Bun**
+ - Все команды должны выполняться с использованием Bun (bun install, bun dev, bun build и т.д.)
+ - При создании скриптов в package.json, они должны быть совместимы с Bun
+
+7. **Язык общения**
+ - Всё общение с пользователем происходит на русском языке
+
+8. **Проверка изменений**
+ - После внесения изменений в код не требуется запускать сервер разработки для проверки
+ - Пользователь самостоятельно запускает сервер и проверяет изменения
+
+9. **Проверка типов данных**
+ - Проверять проект на ошибки типизации через команду `bun run tsc --noEmit -p frontend/tsconfig.json`
+ - В проекте не должно быть типов any
+ - Все интерфейсы компонентов прописывать в файле globalInterfaces.ts
+ - При работе с PocketBase использовать актуальные сигнатуры методов из файла `D:\Verstka\production\astro_minivan\frontend\node_modules\pocketbase\dist\pocketbase.es.d.ts`
+
+10. **Плагин @astrojs/sitemap**
+ - Обязательно к установке в проект пакета @astrojs/sitemap
+ - Обязательно к созданию в проекте файла .nvmrc
+
+11. **Замена хоста при развертывании проекта**
+ - Обязательно нужно изменить http://localhost:3000/ в шаблонах писем на реальный
+
+## Ccылки на документацию
+- URL документации Astro: https://docs.astro.build/en/getting-started/
+- URL документации PocketBase: https://pocketbase.io/docs/
+- URL документации SolidJS: https://docs.solidjs.com/solid-start/getting-started
+- URL документации astro-icons: https://www.astroicon.dev/getting-started/
diff --git a/backend/pb_migrations/1776236760_updated_posts.js b/backend/pb_migrations/1776236760_updated_posts.js
new file mode 100644
index 0000000..e95018b
--- /dev/null
+++ b/backend/pb_migrations/1776236760_updated_posts.js
@@ -0,0 +1,29 @@
+///
+migrate((app) => {
+ const collection = app.findCollectionByNameOrId("pbc_1125843985")
+
+ // remove field
+ collection.fields.removeById("text2548032275")
+
+ return app.save(collection)
+}, (app) => {
+ const collection = app.findCollectionByNameOrId("pbc_1125843985")
+
+ // add field
+ collection.fields.addAt(3, new Field({
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "text2548032275",
+ "max": 0,
+ "min": 0,
+ "name": "imageUrl",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": false,
+ "system": false,
+ "type": "text"
+ }))
+
+ return app.save(collection)
+})
diff --git a/backend/pb_migrations/1776236785_updated_posts.js b/backend/pb_migrations/1776236785_updated_posts.js
new file mode 100644
index 0000000..a054c3e
--- /dev/null
+++ b/backend/pb_migrations/1776236785_updated_posts.js
@@ -0,0 +1,29 @@
+///
+migrate((app) => {
+ const collection = app.findCollectionByNameOrId("pbc_1125843985")
+
+ // add field
+ collection.fields.addAt(11, new Field({
+ "hidden": false,
+ "id": "file3309110367",
+ "maxSelect": 1,
+ "maxSize": 0,
+ "mimeTypes": [],
+ "name": "image",
+ "presentable": false,
+ "protected": false,
+ "required": false,
+ "system": false,
+ "thumbs": [],
+ "type": "file"
+ }))
+
+ return app.save(collection)
+}, (app) => {
+ const collection = app.findCollectionByNameOrId("pbc_1125843985")
+
+ // remove field
+ collection.fields.removeById("file3309110367")
+
+ return app.save(collection)
+})
diff --git a/frontend/package.json b/frontend/package.json
index ca99f49..c6ef46a 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -19,6 +19,7 @@
"astro": "^6.0.8",
"astro-icon": "^1.1.5",
"marked": "^18.0.0",
+ "pocketbase": "^0.21.0",
"tailwindcss": "^4.2.2"
},
"devDependencies": {
diff --git a/frontend/src/components/blog/BlogCard.astro b/frontend/src/components/blog/BlogCard.astro
index 73df787..feee50e 100644
--- a/frontend/src/components/blog/BlogCard.astro
+++ b/frontend/src/components/blog/BlogCard.astro
@@ -6,7 +6,7 @@ export interface Props {
categoryColor?: string;
date: string;
readTime: string;
- imageUrl?: string;
+ image?: string;
slug?: string;
}
@@ -17,9 +17,11 @@ const {
categoryColor = 'bg-gold',
date,
readTime,
- imageUrl = '/images/blog/default.avif',
+ image,
slug = '#'
} = Astro.props;
+
+const imageUrl = image || '/images/blog/default.avif';
---
diff --git a/frontend/src/components/blog/RelatedPosts.astro b/frontend/src/components/blog/RelatedPosts.astro
index 22c1946..93b30f6 100644
--- a/frontend/src/components/blog/RelatedPosts.astro
+++ b/frontend/src/components/blog/RelatedPosts.astro
@@ -1,5 +1,6 @@
---
import BlogCard from '@components/blog/BlogCard.astro';
+import { getPostImageUrl } from '@lib/pb';
interface Post {
id: string;
@@ -10,7 +11,7 @@ interface Post {
categoryColor: string;
date: string;
readTime: string;
- imageUrl: string;
+ image: string;
}
interface Props {
@@ -47,7 +48,7 @@ const filteredPosts = currentSlug
categoryColor={post.categoryColor}
date={formatDate(post.date)}
readTime={post.readTime}
- imageUrl={post.imageUrl}
+ image={getPostImageUrl(post)}
slug={`/blog/${post.slug}`}
/>
))}
diff --git a/frontend/src/lib/pb.ts b/frontend/src/lib/pb.ts
index d2983e8..68dafe6 100644
--- a/frontend/src/lib/pb.ts
+++ b/frontend/src/lib/pb.ts
@@ -4,6 +4,8 @@ const PB_URL = import.meta.env.POCKETBASE_URL || 'http://127.0.0.1:8090';
export const pb = new PocketBase(PB_URL);
+pb.collection('_superusers').authRefresh().catch(() => {});
+
export interface Post {
id: string;
slug: string;
@@ -14,7 +16,7 @@ export interface Post {
categoryColor: string;
date: string;
readTime: string;
- imageUrl: string;
+ image: string;
content?: string;
draft: boolean;
}
@@ -71,4 +73,15 @@ export async function getAllCategories(): Promise {
const categories = (result || []).map((post: any) => post.category).filter(Boolean);
return ['Все', ...new Set(categories)];
+}
+
+export function getPostImageUrl(post: any): 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';
}
\ No newline at end of file
diff --git a/frontend/src/pages/api/posts/index.ts b/frontend/src/pages/api/posts/index.ts
index 522cb05..bfce8b3 100644
--- a/frontend/src/pages/api/posts/index.ts
+++ b/frontend/src/pages/api/posts/index.ts
@@ -1,9 +1,11 @@
import type { APIRoute } from 'astro';
import PocketBase from 'pocketbase';
+const PB_URL = import.meta.env.POCKETBASE_URL || 'http://127.0.0.1:8090';
+
export const GET: APIRoute = async ({ url }) => {
try {
- const pb = new PocketBase(import.meta.env.POCKETBASE_URL);
+ const pb = new PocketBase(PB_URL);
const page = parseInt(url.searchParams.get('page') || '1');
const perPage = parseInt(url.searchParams.get('per_page') || '10');
@@ -25,6 +27,15 @@ export const GET: APIRoute = async ({ url }) => {
sort: '-date',
});
+ const getImageUrl = (post: any) => {
+ if (!post.image) return null;
+ const fileUrl = pb.files.getUrl(post, post.image);
+ if (fileUrl.startsWith('/')) {
+ return `${PB_URL}${fileUrl}`;
+ }
+ return fileUrl;
+ };
+
return new Response(JSON.stringify({
posts: result.items.map(post => ({
id: post.id,
@@ -36,7 +47,7 @@ export const GET: APIRoute = async ({ url }) => {
categoryColor: post.categoryColor,
date: post.date,
readTime: post.readTime,
- imageUrl: post.imageUrl,
+ image: getImageUrl(post),
})),
total: result.totalItems,
page: result.pageInfo.page,
diff --git a/frontend/src/pages/blog/[slug].astro b/frontend/src/pages/blog/[slug].astro
index 7048ac4..3a45293 100644
--- a/frontend/src/pages/blog/[slug].astro
+++ b/frontend/src/pages/blog/[slug].astro
@@ -4,7 +4,7 @@ import { SITE_URL } from '@constants';
import PostCommentForm from '@components/blog/PostCommentForm.astro';
import RelatedPosts from '@components/blog/RelatedPosts.astro';
import ArticleTableOfContents from '@components/blog/ArticleTableOfContents.astro';
-import { getPostBySlug, getPosts } from '@lib/pb';
+import { getPostBySlug, getPosts, getPostImageUrl } from '@lib/pb';
import { marked } from 'marked';
export const prerender = false;
@@ -50,6 +50,7 @@ const { posts: relatedPosts } = await getPosts({ perPage: 4, category: post.cate
const filteredRelated = relatedPosts.filter(p => p.slug !== slug).slice(0, 3);
const currentUrl = `${SITE_URL}/blog/${slug}`;
+const heroImage = getPostImageUrl(post);
---
{
categoryColor={post.categoryColor}
date={formatDate(post.date)}
readTime={post.readTime}
- imageUrl={post.imageUrl}
+ image={getPostImageUrl(post)}
slug={`/blog/${post.slug}`}
/>
diff --git a/frontend/src/pages/blog/page/[page].astro b/frontend/src/pages/blog/page/[page].astro
index 24afb59..69bebf7 100644
--- a/frontend/src/pages/blog/page/[page].astro
+++ b/frontend/src/pages/blog/page/[page].astro
@@ -7,7 +7,7 @@ import BlogCard from '@components/blog/BlogCard.astro';
import Pagination from '@components/base/Pagination.astro';
import CTA from '@components/base/CTA.astro';
import SearchModal from '@components/base/SearchModal.astro';
-import { getPosts, getAllCategories } from '@lib/pb';
+import { getPosts, getAllCategories, getPostImageUrl } from '@lib/pb';
export const prerender = false;
@@ -66,7 +66,7 @@ const formatDate = (date: string) => {
categoryColor={post.categoryColor}
date={formatDate(post.date)}
readTime={post.readTime}
- imageUrl={post.imageUrl}
+ image={getPostImageUrl(post)}
slug={`/blog/${post.slug}`}
/>
diff --git a/frontend/src/pages/blog/search.astro b/frontend/src/pages/blog/search.astro
index 75ce409..b350b03 100644
--- a/frontend/src/pages/blog/search.astro
+++ b/frontend/src/pages/blog/search.astro
@@ -3,7 +3,7 @@ import Layout from '@layouts/Layout.astro';
import { SITE_URL } from '@constants';
import BlogCard from '@components/blog/BlogCard.astro';
import SearchModal from '@components/base/SearchModal.astro';
-import { getPosts } from '@lib/pb';
+import { getPosts, getPostImageUrl } from '@lib/pb';
const url = new URL(Astro.request.url);
const searchQuery = url.searchParams.get('q') || '';
@@ -78,7 +78,7 @@ const formatDate = (date: string) => {
categoryColor={post.categoryColor}
date={formatDate(post.date)}
readTime={post.readTime}
- imageUrl={post.imageUrl}
+ image={getPostImageUrl(post)}
slug={`/blog/${post.slug}`}
/>
))}