From edd730b4386692ebf69637cb436ce1a6efe88776 Mon Sep 17 00:00:00 2001 From: Web-serfer Date: Wed, 15 Apr 2026 02:12:25 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B0=D1=81=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=83=20=D0=B1=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=D0=B0=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=20Backen?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pb_migrations/1776199882_created_posts.js | 37 +++++++++ .../pb_migrations/1776200325_updated_posts.js | 29 +++++++ .../pb_migrations/1776200337_updated_posts.js | 29 +++++++ .../pb_migrations/1776200351_updated_posts.js | 29 +++++++ .../pb_migrations/1776200365_updated_posts.js | 29 +++++++ .../pb_migrations/1776200395_updated_posts.js | 29 +++++++ .../pb_migrations/1776200412_updated_posts.js | 26 ++++++ .../pb_migrations/1776200439_updated_posts.js | 29 +++++++ .../pb_migrations/1776200454_updated_posts.js | 29 +++++++ .../pb_migrations/1776200465_updated_posts.js | 29 +++++++ .../pb_migrations/1776200481_updated_posts.js | 24 ++++++ .../pb_migrations/1776200499_updated_posts.js | 26 ++++++ .../pb_migrations/1776200529_updated_posts.js | 22 +++++ .../pb_migrations/1776200951_updated_posts.js | 22 +++++ bun.lock | 39 ++++++++- frontend/package.json | 1 + .../src/components/base/SearchModal.astro | 51 ++++++------ .../src/components/blog/RelatedPosts.astro | 43 +++++----- frontend/src/lib/pb.ts | 74 +++++++++++++++++ frontend/src/pages/api/auth/sign-in.ts | 45 ++++++++++ frontend/src/pages/api/auth/sign-out.ts | 10 +++ frontend/src/pages/api/auth/sign-up.ts | 45 ++++++++++ frontend/src/pages/api/consultation.ts | 40 +++++++++ frontend/src/pages/api/posts/[slug].ts | 48 +++++++++++ frontend/src/pages/api/posts/index.ts | 54 ++++++++++++ frontend/src/pages/blog/[slug].astro | 76 +++++++---------- frontend/src/pages/blog/index.astro | 43 ++++------ frontend/src/pages/blog/page/[page].astro | 59 +++++-------- frontend/src/pages/blog/search.astro | 50 ++++------- frontend/tsconfig.json | 3 +- package.json | 8 +- scripts/create-posts-collection.ts | 59 +++++++++++++ scripts/migrate-posts.ts | 82 +++++++++++++++++++ 33 files changed, 1019 insertions(+), 200 deletions(-) create mode 100644 backend/pb_migrations/1776199882_created_posts.js create mode 100644 backend/pb_migrations/1776200325_updated_posts.js create mode 100644 backend/pb_migrations/1776200337_updated_posts.js create mode 100644 backend/pb_migrations/1776200351_updated_posts.js create mode 100644 backend/pb_migrations/1776200365_updated_posts.js create mode 100644 backend/pb_migrations/1776200395_updated_posts.js create mode 100644 backend/pb_migrations/1776200412_updated_posts.js create mode 100644 backend/pb_migrations/1776200439_updated_posts.js create mode 100644 backend/pb_migrations/1776200454_updated_posts.js create mode 100644 backend/pb_migrations/1776200465_updated_posts.js create mode 100644 backend/pb_migrations/1776200481_updated_posts.js create mode 100644 backend/pb_migrations/1776200499_updated_posts.js create mode 100644 backend/pb_migrations/1776200529_updated_posts.js create mode 100644 backend/pb_migrations/1776200951_updated_posts.js create mode 100644 frontend/src/lib/pb.ts create mode 100644 frontend/src/pages/api/auth/sign-in.ts create mode 100644 frontend/src/pages/api/auth/sign-out.ts create mode 100644 frontend/src/pages/api/auth/sign-up.ts create mode 100644 frontend/src/pages/api/consultation.ts create mode 100644 frontend/src/pages/api/posts/[slug].ts create mode 100644 frontend/src/pages/api/posts/index.ts create mode 100644 scripts/create-posts-collection.ts create mode 100644 scripts/migrate-posts.ts diff --git a/backend/pb_migrations/1776199882_created_posts.js b/backend/pb_migrations/1776199882_created_posts.js new file mode 100644 index 0000000..512ec42 --- /dev/null +++ b/backend/pb_migrations/1776199882_created_posts.js @@ -0,0 +1,37 @@ +/// +migrate((app) => { + const collection = new Collection({ + "createRule": "@request.auth.id != \"\"", + "deleteRule": "@request.auth.id != \"\"", + "fields": [ + { + "autogeneratePattern": "[a-z0-9]{15}", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + } + ], + "id": "pbc_1125843985", + "indexes": [], + "listRule": "", + "name": "posts", + "system": false, + "type": "base", + "updateRule": "@request.auth.id != \"\"", + "viewRule": "" + }); + + return app.save(collection); +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985"); + + return app.delete(collection); +}) diff --git a/backend/pb_migrations/1776200325_updated_posts.js b/backend/pb_migrations/1776200325_updated_posts.js new file mode 100644 index 0000000..65e0076 --- /dev/null +++ b/backend/pb_migrations/1776200325_updated_posts.js @@ -0,0 +1,29 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // add field + collection.fields.addAt(1, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text724990059", + "max": 0, + "min": 0, + "name": "title", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // remove field + collection.fields.removeById("text724990059") + + return app.save(collection) +}) diff --git a/backend/pb_migrations/1776200337_updated_posts.js b/backend/pb_migrations/1776200337_updated_posts.js new file mode 100644 index 0000000..8702016 --- /dev/null +++ b/backend/pb_migrations/1776200337_updated_posts.js @@ -0,0 +1,29 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // add field + collection.fields.addAt(2, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text1843675174", + "max": 0, + "min": 0, + "name": "description", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // remove field + collection.fields.removeById("text1843675174") + + return app.save(collection) +}) diff --git a/backend/pb_migrations/1776200351_updated_posts.js b/backend/pb_migrations/1776200351_updated_posts.js new file mode 100644 index 0000000..942cc80 --- /dev/null +++ b/backend/pb_migrations/1776200351_updated_posts.js @@ -0,0 +1,29 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // add field + collection.fields.addAt(3, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text3182418120", + "max": 0, + "min": 0, + "name": "author", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // remove field + collection.fields.removeById("text3182418120") + + return app.save(collection) +}) diff --git a/backend/pb_migrations/1776200365_updated_posts.js b/backend/pb_migrations/1776200365_updated_posts.js new file mode 100644 index 0000000..53df4b0 --- /dev/null +++ b/backend/pb_migrations/1776200365_updated_posts.js @@ -0,0 +1,29 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // add field + collection.fields.addAt(4, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text105650625", + "max": 0, + "min": 0, + "name": "category", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // remove field + collection.fields.removeById("text105650625") + + return app.save(collection) +}) diff --git a/backend/pb_migrations/1776200395_updated_posts.js b/backend/pb_migrations/1776200395_updated_posts.js new file mode 100644 index 0000000..e265e93 --- /dev/null +++ b/backend/pb_migrations/1776200395_updated_posts.js @@ -0,0 +1,29 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // add field + collection.fields.addAt(5, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text1898540465", + "max": 0, + "min": 0, + "name": "categoryColor", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // remove field + collection.fields.removeById("text1898540465") + + return app.save(collection) +}) diff --git a/backend/pb_migrations/1776200412_updated_posts.js b/backend/pb_migrations/1776200412_updated_posts.js new file mode 100644 index 0000000..bc52ca3 --- /dev/null +++ b/backend/pb_migrations/1776200412_updated_posts.js @@ -0,0 +1,26 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // add field + collection.fields.addAt(6, new Field({ + "hidden": false, + "id": "date2862495610", + "max": "", + "min": "", + "name": "date", + "presentable": false, + "required": false, + "system": false, + "type": "date" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // remove field + collection.fields.removeById("date2862495610") + + return app.save(collection) +}) diff --git a/backend/pb_migrations/1776200439_updated_posts.js b/backend/pb_migrations/1776200439_updated_posts.js new file mode 100644 index 0000000..08ffdcf --- /dev/null +++ b/backend/pb_migrations/1776200439_updated_posts.js @@ -0,0 +1,29 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // add field + collection.fields.addAt(7, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text1864383797", + "max": 0, + "min": 0, + "name": "readmeTime", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // remove field + collection.fields.removeById("text1864383797") + + return app.save(collection) +}) diff --git a/backend/pb_migrations/1776200454_updated_posts.js b/backend/pb_migrations/1776200454_updated_posts.js new file mode 100644 index 0000000..2171599 --- /dev/null +++ b/backend/pb_migrations/1776200454_updated_posts.js @@ -0,0 +1,29 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // add field + collection.fields.addAt(8, 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) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // remove field + collection.fields.removeById("text2548032275") + + return app.save(collection) +}) diff --git a/backend/pb_migrations/1776200465_updated_posts.js b/backend/pb_migrations/1776200465_updated_posts.js new file mode 100644 index 0000000..3e6b1f0 --- /dev/null +++ b/backend/pb_migrations/1776200465_updated_posts.js @@ -0,0 +1,29 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // add field + collection.fields.addAt(9, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text2560465762", + "max": 0, + "min": 0, + "name": "slug", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // remove field + collection.fields.removeById("text2560465762") + + return app.save(collection) +}) diff --git a/backend/pb_migrations/1776200481_updated_posts.js b/backend/pb_migrations/1776200481_updated_posts.js new file mode 100644 index 0000000..7198662 --- /dev/null +++ b/backend/pb_migrations/1776200481_updated_posts.js @@ -0,0 +1,24 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // add field + collection.fields.addAt(10, new Field({ + "hidden": false, + "id": "bool1182570132", + "name": "draft", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // remove field + collection.fields.removeById("bool1182570132") + + return app.save(collection) +}) diff --git a/backend/pb_migrations/1776200499_updated_posts.js b/backend/pb_migrations/1776200499_updated_posts.js new file mode 100644 index 0000000..d53af72 --- /dev/null +++ b/backend/pb_migrations/1776200499_updated_posts.js @@ -0,0 +1,26 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // add field + collection.fields.addAt(11, new Field({ + "convertURLs": false, + "hidden": false, + "id": "editor4274335913", + "maxSize": 0, + "name": "content", + "presentable": false, + "required": false, + "system": false, + "type": "editor" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // remove field + collection.fields.removeById("editor4274335913") + + return app.save(collection) +}) diff --git a/backend/pb_migrations/1776200529_updated_posts.js b/backend/pb_migrations/1776200529_updated_posts.js new file mode 100644 index 0000000..2e4356e --- /dev/null +++ b/backend/pb_migrations/1776200529_updated_posts.js @@ -0,0 +1,22 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // update collection data + unmarshal({ + "listRule": null, + "viewRule": null + }, collection) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // update collection data + unmarshal({ + "listRule": "", + "viewRule": "" + }, collection) + + return app.save(collection) +}) diff --git a/backend/pb_migrations/1776200951_updated_posts.js b/backend/pb_migrations/1776200951_updated_posts.js new file mode 100644 index 0000000..2b8d334 --- /dev/null +++ b/backend/pb_migrations/1776200951_updated_posts.js @@ -0,0 +1,22 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // update collection data + unmarshal({ + "listRule": " draft = false || @request.auth.id != \"\" ", + "viewRule": " draft = false || @request.auth.id != \"\" " + }, collection) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1125843985") + + // update collection data + unmarshal({ + "listRule": null, + "viewRule": null + }, collection) + + return app.save(collection) +}) diff --git a/bun.lock b/bun.lock index b18d6cd..8ea3467 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,12 @@ "workspaces": { "": { "name": "avtourist086", + "dependencies": { + "pocketbase": "^0.26.8", + }, + "devDependencies": { + "gray-matter": "^4.0.3", + }, }, "frontend": { "name": "frontend", @@ -15,6 +21,7 @@ "@tailwindcss/vite": "^4.2.2", "astro": "^6.0.8", "astro-icon": "^1.1.5", + "marked": "^18.0.0", "tailwindcss": "^4.2.2", }, "devDependencies": { @@ -360,7 +367,7 @@ "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], @@ -494,6 +501,8 @@ "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + "estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="], "estree-util-build-jsx": ["estree-util-build-jsx@3.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="], @@ -516,6 +525,8 @@ "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -554,6 +565,8 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], + "h3": ["h3@1.15.11", "", { "dependencies": { "cookie-es": "^1.2.3", "crossws": "^0.3.5", "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="], "hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], @@ -606,6 +619,8 @@ "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + "is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], @@ -618,12 +633,14 @@ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="], + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], @@ -666,6 +683,8 @@ "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + "marked": ["marked@18.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-2e7Qiv/HJSXj8rDEpgTvGKsP8yYtI9xXHKDnrftrmnrJPaFNM7VRb2YCzWaX4BP1iCJ/XPduzDJZMFoqTCcIMA=="], + "mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="], "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], @@ -848,6 +867,8 @@ "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + "pocketbase": ["pocketbase@0.26.8", "", {}, "sha512-aQ/ewvS7ncvAE8wxoW10iAZu6ElgbeFpBhKPnCfvRovNzm2gW8u/sQNPGN6vNgVEagz44kK//C61oKjfa+7Low=="], + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], @@ -922,6 +943,8 @@ "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], + "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], @@ -946,6 +969,8 @@ "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], "stream-replace-string": ["stream-replace-string@2.0.0", "", {}, "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w=="], @@ -956,6 +981,8 @@ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="], + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], @@ -1108,6 +1135,8 @@ "@astrojs/language-server/@astrojs/compiler": ["@astrojs/compiler@2.13.1", "", {}, "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg=="], + "@astrojs/markdown-remark/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "@iconify/tools/svgo": ["svgo@3.3.3", "", { "dependencies": { "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0", "sax": "^1.5.0" }, "bin": "./bin/svgo" }, "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng=="], "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], @@ -1130,6 +1159,8 @@ "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "astro/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -1150,6 +1181,8 @@ "yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "@astrojs/markdown-remark/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "@iconify/tools/svgo/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], "@iconify/tools/svgo/css-tree": ["css-tree@2.3.1", "", { "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" } }, "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw=="], @@ -1158,6 +1191,8 @@ "@types/yauzl/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + "astro/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], diff --git a/frontend/package.json b/frontend/package.json index fe99e65..ca99f49 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "@tailwindcss/vite": "^4.2.2", "astro": "^6.0.8", "astro-icon": "^1.1.5", + "marked": "^18.0.0", "tailwindcss": "^4.2.2" }, "devDependencies": { diff --git a/frontend/src/components/base/SearchModal.astro b/frontend/src/components/base/SearchModal.astro index dca6f8c..8092e90 100644 --- a/frontend/src/components/base/SearchModal.astro +++ b/frontend/src/components/base/SearchModal.astro @@ -1,26 +1,5 @@ --- -import { getCollection } from 'astro:content'; - -const posts = await getCollection('blog'); - -// Сортируем и форматируем для поиска -const searchData = posts - .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()) - .map(post => ({ - title: post.data.title, - description: post.data.description, - slug: post.id, - category: post.data.category, - categoryColor: post.data.categoryColor, - date: post.data.date.toLocaleDateString('ru-RU', { - day: 'numeric', - month: 'long', - year: 'numeric' - }) - })); - const title = 'Поиск по статьям'; -const postsJson = JSON.stringify(searchData); --- - + \ No newline at end of file diff --git a/frontend/src/pages/blog/page/[page].astro b/frontend/src/pages/blog/page/[page].astro index affd526..24afb59 100644 --- a/frontend/src/pages/blog/page/[page].astro +++ b/frontend/src/pages/blog/page/[page].astro @@ -7,49 +7,28 @@ 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 { getCollection } from 'astro:content'; +import { getPosts, getAllCategories } from '@lib/pb'; -export const prerender = true; - -export async function getStaticPaths() { - const posts = await getCollection('blog'); - const POSTS_PER_PAGE = 6; - const totalPages = Math.ceil(posts.length / POSTS_PER_PAGE); - - return Array.from({ length: totalPages }, (_, i) => ({ - params: { page: String(i + 2) }, // Начинаем со 2-й страницы (1-я это /blog/) - })); -} - -const posts = await getCollection('blog'); - -// Сортируем посты по дате (новые сверху) -const sortedPosts = posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime()); +export const prerender = false; const POSTS_PER_PAGE = 6; const currentPage = Number(Astro.params.page) || 1; -const totalPages = Math.ceil(sortedPosts.length / POSTS_PER_PAGE); -const startIndex = (currentPage - 1) * POSTS_PER_PAGE; -const endIndex = startIndex + POSTS_PER_PAGE; -const paginatedPosts = sortedPosts.slice(startIndex, endIndex); +const { posts, total, totalPages } = await getPosts({ page: currentPage, perPage: POSTS_PER_PAGE }); +const categories = await getAllCategories(); -// Форматируем дату -const formatDate = (date: Date) => { - return date.toLocaleDateString('ru-RU', { +const formatDate = (date: string) => { + return new Date(date).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }); }; - -// Категории -const categories = ['Все', ...new Set(posts.map(post => post.data.category))]; --- post.data.category))]
- {paginatedPosts.map((post: any) => ( -
+ {posts.map((post: any) => ( +
))} @@ -179,4 +158,4 @@ const categories = ['Все', ...new Set(posts.map(post => post.data.category))] setupAnimations(); document.addEventListener('astro:after-swap', setupAnimations); - + \ No newline at end of file diff --git a/frontend/src/pages/blog/search.astro b/frontend/src/pages/blog/search.astro index 4a909e6..75ce409 100644 --- a/frontend/src/pages/blog/search.astro +++ b/frontend/src/pages/blog/search.astro @@ -3,40 +3,23 @@ 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 { getCollection } from 'astro:content'; +import { getPosts } from '@lib/pb'; -const posts = await getCollection('blog'); - -// Получаем параметр поиска из URL const url = new URL(Astro.request.url); const searchQuery = url.searchParams.get('q') || ''; +const { posts: searchResults } = searchQuery + ? await getPosts({ perPage: 20, search: searchQuery }) + : { posts: [], total: 0, page: 1, totalPages: 1 }; + const breadcrumbsItems = [ { label: 'Главная', href: '/' }, { label: 'Блог', href: '/blog' }, { label: searchQuery ? `Поиск: "${searchQuery}"` : 'Поиск' } ]; -// Функция поиска по статьям -function searchArticles(query: string, allPosts: typeof posts) { - if (!query.trim()) return []; - - const lowerQuery = query.toLowerCase(); - - return allPosts.filter(post => { - const titleMatch = post.data.title.toLowerCase().includes(lowerQuery); - const descriptionMatch = post.data.description.toLowerCase().includes(lowerQuery); - const categoryMatch = post.data.category.toLowerCase().includes(lowerQuery); - - return titleMatch || descriptionMatch || categoryMatch; - }); -} - -const searchResults = searchArticles(searchQuery, posts); - -// Форматируем дату -const formatDate = (date: Date) => { - return date.toLocaleDateString('ru-RU', { +const formatDate = (date: string) => { + return new Date(date).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' @@ -89,14 +72,14 @@ const formatDate = (date: Date) => {
{searchResults.map((post: any) => ( ))}
@@ -149,7 +132,6 @@ const formatDate = (date: Date) => { })); }); - // Автоматически открываем поиск при загрузке страницы без параметров document.addEventListener('DOMContentLoaded', () => { const urlParams = new URLSearchParams(window.location.search); const query = urlParams.get('q'); @@ -338,4 +320,4 @@ const formatDate = (date: Date) => { font-size: 1.5rem; } } - + \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 5a40763..1851f40 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -12,7 +12,8 @@ "@scripts/*": ["src/scripts/*"], "@layouts/*": ["src/layouts/*"], "@assets/*": ["src/assets/*"], - "@pages/*": ["src/pages/*"] + "@pages/*": ["src/pages/*"], + "@lib/*": ["src/lib/*"] } } } diff --git a/package.json b/package.json index 9195490..85b4bf7 100644 --- a/package.json +++ b/package.json @@ -12,5 +12,11 @@ }, "workspaces": [ "frontend" - ] + ], + "dependencies": { + "pocketbase": "^0.26.8" + }, + "devDependencies": { + "gray-matter": "^4.0.3" + } } diff --git a/scripts/create-posts-collection.ts b/scripts/create-posts-collection.ts new file mode 100644 index 0000000..79ef382 --- /dev/null +++ b/scripts/create-posts-collection.ts @@ -0,0 +1,59 @@ +import PocketBase from 'pocketbase'; + +const PB_URL = process.env.POCKETBASE_URL || 'http://127.0.0.1:8090'; +const ADMIN_EMAIL = process.env.PB_ADMIN_EMAIL; +const ADMIN_PASSWORD = process.env.PB_ADMIN_PASSWORD; + +async function createCollection() { + if (!ADMIN_EMAIL || !ADMIN_PASSWORD) { + console.error('❌ Укажите PB_ADMIN_EMAIL и PB_ADMIN_PASSWORD в .env'); + process.exit(1); + } + + const pb = new PocketBase(PB_URL); + + try { + await pb.admins.authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD); + console.log('✅ Подключено к PocketBase'); + } catch (e) { + console.error('❌ Ошибка авторизации'); + process.exit(1); + } + + try { + const collection = await pb.collections.create({ + name: 'posts', + type: 'base', + system: false, + schema: [ + { name: 'title', type: 'text', required: true }, + { name: 'description', type: 'text', required: true }, + { name: 'author', type: 'text', required: false }, + { name: 'category', type: 'text', required: false }, + { name: 'categoryColor', type: 'text', required: false }, + { name: 'date', type: 'date', required: true }, + { name: 'readTime', type: 'text', required: false }, + { name: 'imageUrl', type: 'text', required: false }, + { name: 'slug', type: 'text', required: true }, + { name: 'draft', type: 'bool', required: false }, + { name: 'content', type: 'editor', required: false }, + ], + listRule: '', + viewRule: '', + createRule: '@request.auth.id != ""', + updateRule: '@request.auth.id != ""', + deleteRule: '@request.auth.id != ""', + }); + + console.log('✅ Коллекция "posts" создана'); + console.log(' ID:', collection.id); + } catch (e: any) { + if (e.data?.message?.includes('already exists')) { + console.log('ℹ️ Коллекция "posts" уже существует'); + } else { + console.error('❌ Ошибка:', e.data || e.message); + } + } +} + +createCollection(); \ No newline at end of file diff --git a/scripts/migrate-posts.ts b/scripts/migrate-posts.ts new file mode 100644 index 0000000..14f57af --- /dev/null +++ b/scripts/migrate-posts.ts @@ -0,0 +1,82 @@ +import PocketBase from 'pocketbase'; +import fs from 'fs'; +import path from 'path'; +import matter from 'gray-matter'; + +const PB_URL = process.env.POCKETBASE_URL || 'http://127.0.0.1:8090'; + +const ADMIN_EMAIL = process.env.PB_ADMIN_EMAIL; +const ADMIN_PASSWORD = process.env.PB_ADMIN_PASSWORD; + +interface PostFrontmatter { + title: string; + description: string; + author: string; + category: string; + categoryColor: string; + date: string; + readTime: string; + imageUrl: string; + draft: boolean; +} + +async function migrate() { + console.log('🔄 Миграция постов в PocketBase...\n'); + + if (!ADMIN_EMAIL || !ADMIN_PASSWORD) { + console.error('❌ Укажите PB_ADMIN_EMAIL и PB_ADMIN_PASSWORD в .env'); + process.exit(1); + } + + const pb = new PocketBase(PB_URL); + + try { + await pb.admins.authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD); + console.log('✅ Подключено к PocketBase'); + } catch (e) { + console.error('❌ Ошибка авторизации в PocketBase'); + process.exit(1); + } + + const blogDir = path.join(process.cwd(), 'frontend/src/content/blog'); + const files = fs.readdirSync(blogDir).filter(f => f.endsWith('.mdx')); + + console.log(`📂 Найдено ${files.length} постов в ${blogDir}\n`); + + let migrated = 0; + let skipped = 0; + + for (const file of files) { + const filePath = path.join(blogDir, file); + const fileContent = fs.readFileSync(filePath, 'utf-8'); + const { data, content } = matter(fileContent); + + const frontmatter = data as PostFrontmatter; + const slug = file.replace('.mdx', ''); + + try { + const record = await pb.collection('posts').create({ + title: frontmatter.title, + description: frontmatter.description, + author: frontmatter.author, + category: frontmatter.category, + categoryColor: frontmatter.categoryColor, + date: frontmatter.date, + readTime: frontmatter.readTime, + imageUrl: frontmatter.imageUrl, + slug: slug, + draft: frontmatter.draft ?? false, + content: content, + }); + + console.log(`✅ Мигрирован: ${slug}`); + migrated++; + } catch (e: any) { + console.error(`❌ Ошибка миграции ${slug}:`, e.response?.data || e.message); + } + } + + console.log(`\n📊 Готово! Мигрировано: ${migrated}, пропущено: ${skipped}`); +} + +migrate().catch(console.error); \ No newline at end of file