Новые изменения в коде проекта
This commit is contained in:
parent
0777fc201f
commit
aef12e853d
12 changed files with 159 additions and 15 deletions
57
AGENTS.md
Normal file
57
AGENTS.md
Normal file
|
|
@ -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/
|
||||||
29
backend/pb_migrations/1776236760_updated_posts.js
Normal file
29
backend/pb_migrations/1776236760_updated_posts.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
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)
|
||||||
|
})
|
||||||
29
backend/pb_migrations/1776236785_updated_posts.js
Normal file
29
backend/pb_migrations/1776236785_updated_posts.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
"astro": "^6.0.8",
|
"astro": "^6.0.8",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"marked": "^18.0.0",
|
"marked": "^18.0.0",
|
||||||
|
"pocketbase": "^0.21.0",
|
||||||
"tailwindcss": "^4.2.2"
|
"tailwindcss": "^4.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export interface Props {
|
||||||
categoryColor?: string;
|
categoryColor?: string;
|
||||||
date: string;
|
date: string;
|
||||||
readTime: string;
|
readTime: string;
|
||||||
imageUrl?: string;
|
image?: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,9 +17,11 @@ const {
|
||||||
categoryColor = 'bg-gold',
|
categoryColor = 'bg-gold',
|
||||||
date,
|
date,
|
||||||
readTime,
|
readTime,
|
||||||
imageUrl = '/images/blog/default.avif',
|
image,
|
||||||
slug = '#'
|
slug = '#'
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
|
const imageUrl = image || '/images/blog/default.avif';
|
||||||
---
|
---
|
||||||
|
|
||||||
<article class="blog-card" data-animation="fade-up">
|
<article class="blog-card" data-animation="fade-up">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
---
|
---
|
||||||
import BlogCard from '@components/blog/BlogCard.astro';
|
import BlogCard from '@components/blog/BlogCard.astro';
|
||||||
|
import { getPostImageUrl } from '@lib/pb';
|
||||||
|
|
||||||
interface Post {
|
interface Post {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -10,7 +11,7 @@ interface Post {
|
||||||
categoryColor: string;
|
categoryColor: string;
|
||||||
date: string;
|
date: string;
|
||||||
readTime: string;
|
readTime: string;
|
||||||
imageUrl: string;
|
image: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -47,7 +48,7 @@ const filteredPosts = currentSlug
|
||||||
categoryColor={post.categoryColor}
|
categoryColor={post.categoryColor}
|
||||||
date={formatDate(post.date)}
|
date={formatDate(post.date)}
|
||||||
readTime={post.readTime}
|
readTime={post.readTime}
|
||||||
imageUrl={post.imageUrl}
|
image={getPostImageUrl(post)}
|
||||||
slug={`/blog/${post.slug}`}
|
slug={`/blog/${post.slug}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
export const pb = new PocketBase(PB_URL);
|
||||||
|
|
||||||
|
pb.collection('_superusers').authRefresh().catch(() => {});
|
||||||
|
|
||||||
export interface Post {
|
export interface Post {
|
||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
|
@ -14,7 +16,7 @@ export interface Post {
|
||||||
categoryColor: string;
|
categoryColor: string;
|
||||||
date: string;
|
date: string;
|
||||||
readTime: string;
|
readTime: string;
|
||||||
imageUrl: string;
|
image: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
draft: boolean;
|
draft: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -71,4 +73,15 @@ export async function getAllCategories(): Promise<string[]> {
|
||||||
|
|
||||||
const categories = (result || []).map((post: any) => post.category).filter(Boolean);
|
const categories = (result || []).map((post: any) => post.category).filter(Boolean);
|
||||||
return ['Все', ...new Set(categories)];
|
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';
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import type { APIRoute } from 'astro';
|
import type { APIRoute } from 'astro';
|
||||||
import PocketBase from 'pocketbase';
|
import PocketBase from 'pocketbase';
|
||||||
|
|
||||||
|
const PB_URL = import.meta.env.POCKETBASE_URL || 'http://127.0.0.1:8090';
|
||||||
|
|
||||||
export const GET: APIRoute = async ({ url }) => {
|
export const GET: APIRoute = async ({ url }) => {
|
||||||
try {
|
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 page = parseInt(url.searchParams.get('page') || '1');
|
||||||
const perPage = parseInt(url.searchParams.get('per_page') || '10');
|
const perPage = parseInt(url.searchParams.get('per_page') || '10');
|
||||||
|
|
@ -25,6 +27,15 @@ export const GET: APIRoute = async ({ url }) => {
|
||||||
sort: '-date',
|
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({
|
return new Response(JSON.stringify({
|
||||||
posts: result.items.map(post => ({
|
posts: result.items.map(post => ({
|
||||||
id: post.id,
|
id: post.id,
|
||||||
|
|
@ -36,7 +47,7 @@ export const GET: APIRoute = async ({ url }) => {
|
||||||
categoryColor: post.categoryColor,
|
categoryColor: post.categoryColor,
|
||||||
date: post.date,
|
date: post.date,
|
||||||
readTime: post.readTime,
|
readTime: post.readTime,
|
||||||
imageUrl: post.imageUrl,
|
image: getImageUrl(post),
|
||||||
})),
|
})),
|
||||||
total: result.totalItems,
|
total: result.totalItems,
|
||||||
page: result.pageInfo.page,
|
page: result.pageInfo.page,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { SITE_URL } from '@constants';
|
||||||
import PostCommentForm from '@components/blog/PostCommentForm.astro';
|
import PostCommentForm from '@components/blog/PostCommentForm.astro';
|
||||||
import RelatedPosts from '@components/blog/RelatedPosts.astro';
|
import RelatedPosts from '@components/blog/RelatedPosts.astro';
|
||||||
import ArticleTableOfContents from '@components/blog/ArticleTableOfContents.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';
|
import { marked } from 'marked';
|
||||||
|
|
||||||
export const prerender = false;
|
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 filteredRelated = relatedPosts.filter(p => p.slug !== slug).slice(0, 3);
|
||||||
|
|
||||||
const currentUrl = `${SITE_URL}/blog/${slug}`;
|
const currentUrl = `${SITE_URL}/blog/${slug}`;
|
||||||
|
const heroImage = getPostImageUrl(post);
|
||||||
---
|
---
|
||||||
|
|
||||||
<ArticleLayout
|
<ArticleLayout
|
||||||
|
|
@ -61,7 +62,7 @@ const currentUrl = `${SITE_URL}/blog/${slug}`;
|
||||||
{ label: 'Блог', href: '/blog' },
|
{ label: 'Блог', href: '/blog' },
|
||||||
{ label: post.title }
|
{ label: post.title }
|
||||||
]}
|
]}
|
||||||
heroImage={post.imageUrl}
|
heroImage={heroImage}
|
||||||
heroAlt={post.title}
|
heroAlt={post.title}
|
||||||
category={post.category}
|
category={post.category}
|
||||||
postTitle={post.title}
|
postTitle={post.title}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import BlogCard from '@components/blog/BlogCard.astro';
|
||||||
import Pagination from '@components/base/Pagination.astro';
|
import Pagination from '@components/base/Pagination.astro';
|
||||||
import CTA from '@components/base/CTA.astro';
|
import CTA from '@components/base/CTA.astro';
|
||||||
import SearchModal from '@components/base/SearchModal.astro';
|
import SearchModal from '@components/base/SearchModal.astro';
|
||||||
import { getPosts, getAllCategories } from '@lib/pb';
|
import { getPosts, getAllCategories, getPostImageUrl } from '@lib/pb';
|
||||||
|
|
||||||
const POSTS_PER_PAGE = 6;
|
const POSTS_PER_PAGE = 6;
|
||||||
const currentPage = 1;
|
const currentPage = 1;
|
||||||
|
|
@ -64,7 +64,7 @@ const formatDate = (date: string) => {
|
||||||
categoryColor={post.categoryColor}
|
categoryColor={post.categoryColor}
|
||||||
date={formatDate(post.date)}
|
date={formatDate(post.date)}
|
||||||
readTime={post.readTime}
|
readTime={post.readTime}
|
||||||
imageUrl={post.imageUrl}
|
image={getPostImageUrl(post)}
|
||||||
slug={`/blog/${post.slug}`}
|
slug={`/blog/${post.slug}`}
|
||||||
/>
|
/>
|
||||||
</article>
|
</article>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import BlogCard from '@components/blog/BlogCard.astro';
|
||||||
import Pagination from '@components/base/Pagination.astro';
|
import Pagination from '@components/base/Pagination.astro';
|
||||||
import CTA from '@components/base/CTA.astro';
|
import CTA from '@components/base/CTA.astro';
|
||||||
import SearchModal from '@components/base/SearchModal.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;
|
export const prerender = false;
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ const formatDate = (date: string) => {
|
||||||
categoryColor={post.categoryColor}
|
categoryColor={post.categoryColor}
|
||||||
date={formatDate(post.date)}
|
date={formatDate(post.date)}
|
||||||
readTime={post.readTime}
|
readTime={post.readTime}
|
||||||
imageUrl={post.imageUrl}
|
image={getPostImageUrl(post)}
|
||||||
slug={`/blog/${post.slug}`}
|
slug={`/blog/${post.slug}`}
|
||||||
/>
|
/>
|
||||||
</article>
|
</article>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import Layout from '@layouts/Layout.astro';
|
||||||
import { SITE_URL } from '@constants';
|
import { SITE_URL } from '@constants';
|
||||||
import BlogCard from '@components/blog/BlogCard.astro';
|
import BlogCard from '@components/blog/BlogCard.astro';
|
||||||
import SearchModal from '@components/base/SearchModal.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 url = new URL(Astro.request.url);
|
||||||
const searchQuery = url.searchParams.get('q') || '';
|
const searchQuery = url.searchParams.get('q') || '';
|
||||||
|
|
@ -78,7 +78,7 @@ const formatDate = (date: string) => {
|
||||||
categoryColor={post.categoryColor}
|
categoryColor={post.categoryColor}
|
||||||
date={formatDate(post.date)}
|
date={formatDate(post.date)}
|
||||||
readTime={post.readTime}
|
readTime={post.readTime}
|
||||||
imageUrl={post.imageUrl}
|
image={getPostImageUrl(post)}
|
||||||
slug={`/blog/${post.slug}`}
|
slug={`/blog/${post.slug}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue