use strapi client, use locals for strapi url
All checks were successful
Deployment / Deploy website (push) Successful in 2m56s
Deployment / Deploy strapi (push) Successful in 3m21s

This commit is contained in:
Benjamin Palko 2025-05-06 11:53:45 -04:00
parent 817c3a78ac
commit 0e94cb95ca
11 changed files with 144 additions and 110 deletions

View file

@ -4,6 +4,7 @@
"": {
"name": "benjaminpalko.github.io",
"dependencies": {
"@strapi/client": "^1.4.0",
"dayjs": "^1.11.13",
"lucide-svelte": "^0.503.0",
"nodemailer": "^6.10.1",
@ -507,6 +508,8 @@
"@strapi/admin": ["@strapi/admin@5.12.7", "", { "dependencies": { "@casl/ability": "6.5.0", "@internationalized/date": "3.5.4", "@radix-ui/react-context": "1.0.1", "@radix-ui/react-toolbar": "1.0.4", "@reduxjs/toolkit": "1.9.7", "@strapi/design-system": "2.0.0-rc.23", "@strapi/icons": "2.0.0-rc.23", "@strapi/permissions": "5.12.7", "@strapi/types": "5.12.7", "@strapi/typescript-utils": "5.12.7", "@strapi/utils": "5.12.7", "@testing-library/dom": "10.1.0", "@testing-library/react": "15.0.7", "@testing-library/user-event": "14.5.2", "axios": "1.8.4", "bcryptjs": "2.4.3", "boxen": "5.1.2", "chalk": "^4.1.2", "codemirror5": "npm:codemirror@^5.65.11", "cross-env": "^7.0.3", "date-fns": "2.30.0", "execa": "5.1.1", "fast-deep-equal": "3.1.3", "formik": "2.4.5", "fractional-indexing": "3.2.0", "fs-extra": "11.2.0", "highlight.js": "^10.4.1", "immer": "9.0.21", "inquirer": "8.2.5", "invariant": "^2.2.4", "is-localhost-ip": "2.0.0", "jsonwebtoken": "9.0.0", "koa": "2.15.4", "koa-compose": "4.1.0", "koa-passport": "6.0.0", "koa-static": "5.0.0", "koa2-ratelimit": "^1.1.3", "lodash": "4.17.21", "node-schedule": "2.1.1", "ora": "5.4.1", "p-map": "4.0.0", "passport-local": "1.0.0", "pluralize": "8.0.0", "punycode": "2.3.1", "qs": "6.11.1", "react-dnd": "16.0.1", "react-dnd-html5-backend": "16.0.1", "react-intl": "6.6.2", "react-is": "^18.2.0", "react-query": "3.39.3", "react-redux": "8.1.3", "react-select": "5.8.0", "react-window": "1.8.10", "rimraf": "5.0.5", "sanitize-html": "2.13.0", "scheduler": "0.23.0", "semver": "7.5.4", "sift": "16.0.1", "typescript": "5.4.4", "use-context-selector": "1.4.1", "yup": "0.32.9", "zod": "^3.22.4" }, "peerDependencies": { "@strapi/data-transfer": "^5.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", "react-router-dom": "^6.0.0", "styled-components": "^6.0.0" } }, "sha512-YpyPw1ukgDY2NypRurrAcgnfte9GtcIMc4fOqBgntMtv/hFV6cIBUeDwIZmBJIruAqYGlxApZY7eeSx6yCi3Zg=="],
"@strapi/client": ["@strapi/client@1.4.0", "", { "dependencies": { "debug": "4.4.0", "qs": "6.14.0" } }, "sha512-A4mS6y52sI5CP9LOgB2iB5NOh1w1nonRlF4tWWtAe4+qquO59fp1tXOf08A1oXnSST1NSSifpQwrUiHi4LmWwQ=="],
"@strapi/cloud-cli": ["@strapi/cloud-cli@5.12.7", "", { "dependencies": { "@strapi/utils": "5.12.7", "axios": "1.8.4", "boxen": "5.1.2", "chalk": "4.1.2", "cli-progress": "3.12.0", "commander": "8.3.0", "eventsource": "2.0.2", "fast-safe-stringify": "2.1.1", "fs-extra": "11.2.0", "inquirer": "8.2.5", "jsonwebtoken": "9.0.0", "jwks-rsa": "3.1.0", "lodash": "4.17.21", "minimatch": "9.0.3", "open": "8.4.0", "ora": "5.4.1", "pkg-up": "3.1.0", "tar": "6.2.1", "xdg-app-paths": "8.3.0", "yup": "0.32.9" }, "bin": { "cloud-cli": "bin/index.js" } }, "sha512-TFCvYF7AIHndx9hUUX39fUeK4+udoAVfj9YhByTds5KBuiGos2LwZJRQGgjKRmI1FdUGMjfWL+IFUIN22XksTw=="],
"@strapi/content-manager": ["@strapi/content-manager@5.12.7", "", { "dependencies": { "@radix-ui/react-toolbar": "1.0.4", "@reduxjs/toolkit": "1.9.7", "@sindresorhus/slugify": "1.1.0", "@strapi/design-system": "2.0.0-rc.23", "@strapi/icons": "2.0.0-rc.23", "@strapi/types": "5.12.7", "@strapi/utils": "5.12.7", "codemirror5": "npm:codemirror@^5.65.11", "date-fns": "2.30.0", "fractional-indexing": "3.2.0", "highlight.js": "^10.4.1", "immer": "9.0.21", "koa": "2.15.4", "lodash": "4.17.21", "markdown-it": "^13.0.2", "markdown-it-abbr": "^1.0.4", "markdown-it-container": "^3.0.0", "markdown-it-deflist": "^2.1.0", "markdown-it-emoji": "^2.0.0", "markdown-it-footnote": "^3.0.3", "markdown-it-ins": "^3.0.1", "markdown-it-mark": "^3.0.1", "markdown-it-sub": "^1.0.0", "markdown-it-sup": "1.0.0", "node-schedule": "2.1.1", "prismjs": "1.30.0", "qs": "6.11.1", "react-dnd": "16.0.1", "react-dnd-html5-backend": "16.0.1", "react-helmet": "^6.1.0", "react-intl": "6.6.2", "react-query": "3.39.3", "react-redux": "8.1.3", "react-window": "1.8.10", "sanitize-html": "2.13.0", "slate": "0.94.1", "slate-history": "0.93.0", "slate-react": "0.98.3", "yup": "0.32.9" }, "peerDependencies": { "@strapi/admin": "^5.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", "react-router-dom": "^6.0.0", "styled-components": "^6.0.0" } }, "sha512-c7jlAUx3aDKFSt77XnzVWmyK/kqTo9uvZwfOg4n3ay6znG83zAd1TiggVSsr/LrQE4z9i153PUIC6lFt0poPrQ=="],

View file

@ -46,6 +46,7 @@
"vitest": "^3.1.2"
},
"dependencies": {
"@strapi/client": "^1.4.0",
"dayjs": "^1.11.13",
"lucide-svelte": "^0.503.0",
"nodemailer": "^6.10.1"

6
src/app.d.ts vendored
View file

@ -3,7 +3,11 @@
declare global {
namespace App {
// interface Error {}
// interface Locals {}
interface Locals {
Strapi: {
url: string;
};
}
// interface PageData {}
// interface PageState {}
// interface Platform {}

View file

@ -1,4 +1,5 @@
import type { HandleServerError } from '@sveltejs/kit';
import { env } from '$env/dynamic/private';
import type { Handle, HandleServerError } from '@sveltejs/kit';
import { randomUUID } from 'crypto';
export const handleError: HandleServerError = async ({ error, event, message, status }) => {
@ -11,3 +12,10 @@ export const handleError: HandleServerError = async ({ error, event, message, st
errorId
};
};
export const handle: Handle = async ({ event, resolve }) => {
event.locals.Strapi.url = `http://${env.STRAPI_HOST}:${env.STRAPI_PORT}`;
const response = await resolve(event);
return response;
};

View file

@ -3,24 +3,24 @@
import { Link } from 'lucide-svelte';
interface Props {
documentId: string;
id: string;
img: string;
'img-alt': string | null;
title: string;
published: string;
topics: { id: number; Topic: string }[];
topics: { id: number; name: string }[];
}
let { documentId, img, 'img-alt': alt, title, published, topics }: Props = $props();
let { id, img, 'img-alt': alt, title, published, topics }: Props = $props();
let blogLink = $derived(`/blog/${documentId}`);
let blogLink = $derived(`/blog/${id}`);
</script>
<div class="card">
<figure class="rounded-box group">
<div class="absolute top-4 left-4 z-10 flex gap-2">
{#each topics as topic (topic.id)}
<div class="badge badge-soft badge-info">{topic.Topic}</div>
<div class="badge badge-soft badge-info">{topic.name}</div>
{/each}
</div>
<div class="absolute z-10 opacity-0 transition-opacity group-hover:opacity-75">

56
src/lib/types/Blog.ts Normal file
View file

@ -0,0 +1,56 @@
type Dates = {
createdAt: string;
updateAt: string;
publishedAt: string;
};
export interface BlogPost extends Dates {
id: number;
documentId: string;
Title: string;
Header: Image;
Body: string;
Topics: Topic[];
}
interface Topic {
id: number;
Topic: string;
}
interface ImageFormat {
ext: string;
url: string;
hash: string;
mime: string;
name: string;
path: unknown;
size: number;
width: number;
height: number;
sizeInBytes: number;
}
interface Image extends Dates {
id: number;
documentId: string;
name: string;
alternativeText: string | null;
caption: string | null;
width: string;
height: string;
formats: {
large: ImageFormat;
medium: ImageFormat;
small: ImageFormat;
thumbnail: ImageFormat;
};
hash: string;
ext: string;
mime: string;
size: number;
url: string;
previewUrl: string | null;
provider: string;
provider_metadata: unknown | null;
}

0
src/lib/types/index.ts Normal file
View file

View file

@ -1,89 +1,39 @@
import { env } from '$env/dynamic/private';
import { strapi } from '@strapi/client';
import type { PageServerLoad } from './$types';
type Dates = {
createdAt: string;
updateAt: string;
publishedAt: string;
};
export const load: PageServerLoad = async ({ locals }) => {
const { Strapi } = locals;
interface BlogPost extends Dates {
id: number;
documentId: string;
Title: string;
Header: Image;
Body: string;
Topics: Topic[];
}
const client = strapi({
baseURL: Strapi.url,
auth: env.STRAPI_TOKEN
});
interface Topic {
id: number;
Topic: string;
}
const blogs = client.collection('blogs');
interface ImageFormat {
ext: string;
url: string;
hash: string;
mime: string;
name: string;
path: unknown;
size: number;
width: number;
height: number;
sizeInBytes: number;
}
interface Image extends Dates {
id: number;
documentId: string;
name: string;
alternativeText: string | null;
caption: string | null;
width: string;
height: string;
formats: {
large: ImageFormat;
medium: ImageFormat;
small: ImageFormat;
thumbnail: ImageFormat;
};
hash: string;
ext: string;
mime: string;
size: number;
url: string;
previewUrl: string | null;
provider: string;
provider_metadata: unknown | null;
}
interface Meta {
pagingation: {
page: number;
pageSize: number;
pageCount: number;
total: number;
};
}
export const load: PageServerLoad = async () => {
const response = await fetch(
`http://${env.STRAPI_HOST}:${env.STRAPI_PORT}/api/blogs?populate=Header&populate=Topics`,
{
method: 'GET',
headers: {
Authorization: `bearer ${env.STRAPI_TOKEN}`
}
}
);
const { data, meta }: { data: BlogPost[]; meta: Meta } = await response.json();
console.log(JSON.stringify(data, null, 2));
const { data, meta } = await blogs.find({
populate: ['Header', 'Topics']
});
return {
posts: data,
strapi: {
url: Strapi.url
},
posts: data.map((post) => ({
id: post.documentId,
image: {
url: `${Strapi.url}${post.Header.formats.medium.url}`,
alt: post.Header.alternativeText
},
title: post.Title,
body: post.Body,
topics: post.Topics.map((topic: { id: string; Topic: string }) => ({
id: topic.id,
name: topic.Topic
})),
published: post.publishedAt
})),
meta
};
};

View file

@ -1,5 +1,4 @@
<script lang="ts">
import { env } from '$env/dynamic/public';
import BlogCard from '$lib/components/BlogCard.svelte';
import type { PageProps } from './$types';
@ -15,12 +14,12 @@
<div class="mx-auto grid gap-8 px-3 py-3 sm:px-8 sm:py-4 lg:grid-cols-2">
{#each posts as post (post.id)}
<BlogCard
documentId={post.documentId}
img={`${env.PUBLIC_STRAPI_HOST}${post.Header.formats.medium.url}`}
img-alt={post.Header.alternativeText}
title={post.Title}
published={post.publishedAt}
topics={post.Topics}
id={post.id}
img={post.image.url}
img-alt={post.image.alt}
title={post.title}
published={post.published}
topics={post.topics}
/>
{/each}
</div>

View file

@ -1,24 +1,37 @@
import { env } from '$env/dynamic/private';
import { strapi } from '@strapi/client';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
export const load: PageServerLoad = async ({ locals, params }) => {
const { slug } = params;
const response = await fetch(
`http://${env.STRAPI_HOST}:${env.STRAPI_PORT}/api/blogs/${slug}?populate=Header&populate=Topics`,
{
method: 'GET',
headers: {
Authorization: `bearer ${env.STRAPI_TOKEN}`
}
}
);
const { Strapi } = locals;
const { data, meta } = await response.json();
const client = strapi({
baseURL: Strapi.url,
auth: env.STRAPI_TOKEN
});
console.log(JSON.stringify(data, null, 2));
const blogs = client.collection('blogs');
const { data: post, meta } = await blogs.findOne(slug, {
populate: ['Header', 'Topics']
});
return {
post: data,
post: {
id: post.documentId,
image: {
url: `${Strapi.url}${post.Header.url}`,
alt: post.Header.alternativeText
},
title: post.Title,
body: post.Body,
topics: post.Topics.map((topic: { id: string; Topic: string }) => ({
id: topic.id,
name: topic.Topic
})),
published: post.publishedAt
},
meta
};
};

View file

@ -11,16 +11,16 @@
<div class="flex flex-col items-center gap-4">
<div class="w-full max-w-5xl px-4">
<p class="text-gray-500">{dayjs(post.createdAt).format('DD MMMM YYYY')}</p>
<h1 class="text-4xl font-light">{post.Title}</h1>
<p class="text-gray-500">{dayjs(post.published).format('DD MMMM YYYY')}</p>
<h1 class="text-4xl font-light">{post.title}</h1>
</div>
<img
class="aspect-4/2 w-full object-cover"
src={`${env.PUBLIC_STRAPI_HOST}${post.Header.formats.medium.url}`}
alt={post.Header.alternativeText}
src={`${env.PUBLIC_STRAPI_HOST}${post.image.url}`}
alt={post.image.alt}
/>
<div class="w-full max-w-5xl px-4">
{#await compile(post.Body)}
{#await compile(post.body)}
<div class="skeleton h-48 w-full"></div>
{:then content}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->