Compare commits

...

29 Commits

Author SHA1 Message Date
tobias
e9fb68ec9f Fix typo in build script
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-04 10:02:40 +02:00
tobias
f37a1719b7 Update payload to 2.18.3
Some checks failed
continuous-integration/drone/push Build is failing
2024-05-27 11:24:42 +02:00
tobias
d62e7d788e Restore dev, build & gen:types scripts
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-25 08:18:33 +02:00
tobias
8fdfb2fbe4 Check if dev script is stopping build (??)
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-24 09:10:43 +02:00
tobias
0bb0644b55 Move payload types to within payload
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-22 09:21:31 +02:00
tobias
b85c62f3fc Try to make build work
Some checks failed
continuous-integration/drone/push Build is failing
2024-05-22 09:13:09 +02:00
3wc
910f943a2d Tweak Drone pipeline to trigger build
Some checks reported errors
continuous-integration/drone/push Build was killed
2024-05-21 18:15:14 -03:00
tobias
10cb01964b Change order of payload build steps
Some checks failed
continuous-integration/drone/push Build is failing
2024-05-21 12:52:07 +02:00
tobias
91073bd498 Generate types before building payload
Some checks failed
continuous-integration/drone/push Build is failing
2024-05-21 12:22:55 +02:00
tobias
cc0a5cb1c5 Add types dir to payload tsconfig
Some checks failed
continuous-integration/drone/push Build is failing
2024-05-21 12:14:53 +02:00
tobias
7dbc81c5b2 Add more blocks to posts
Some checks failed
continuous-integration/drone/push Build is failing
2024-05-21 12:07:14 +02:00
tobias
4b30d58db6 Type posts 2024-05-21 12:04:23 +02:00
tobias
62e50496a2 Merge branch 'main' into t-work 2024-05-21 11:39:36 +02:00
tobias
2a494425ad Create listen script for payload types
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-21 11:39:10 +02:00
tobias
b91dd893a4 Collection access tweaks 2024-05-21 11:17:51 +02:00
tobias
691729c53c Create author component 2024-05-21 11:17:40 +02:00
tobias
09ad9bdc6d Move payload types again
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-20 15:58:11 +02:00
tobias
f94bc5d822 Move payload types so dev container doesn't crash
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-20 14:20:26 +02:00
tobias
370eac2b25 Save generated payload types in astro types folder
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-20 14:06:01 +02:00
tobias
ba6ca90c3a Fix css typo
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-20 13:40:01 +02:00
tobias
985774bf24 Add payload seed 2024-05-20 13:31:57 +02:00
tobias
747ada89e9 Update ssg and editor access functions
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-20 11:41:41 +02:00
tobias
ff564a62ec Add author collection 2024-05-20 11:40:57 +02:00
tobias
9629c93ceb Merge branch 'main' into t-work 2024-05-20 09:43:08 +02:00
tobias
d7f22fdd5f Add posts full CRUD for editor role 2024-05-20 09:42:31 +02:00
tobias
1ccf660f5b Configure user access 2024-05-20 09:10:08 +02:00
tobias
1dbb075cd8 Style h4, h5, h6 2024-05-20 08:22:47 +02:00
tobias
386b5c17f9 Create 404 page 2024-05-20 08:17:41 +02:00
tobias
f017fa28dc Restrict user collection to admins 2024-05-19 23:17:39 +02:00
28 changed files with 3047 additions and 1749 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
data
yarn-debug.log*
yarn-error.log*
#payload-types.ts

5
astro/.gitignore vendored
View File

@ -15,7 +15,4 @@ pnpm-debug.log*
# macOS-specific files
.DS_Store
# types
src/types.ts
.DS_Store

36
astro/src/assets/404.svg Normal file
View File

@ -0,0 +1,36 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 500 500" style="width: 178px;" xml:space="preserve" data-imageid="404-page-not-found-1-77" imageName="404 Page Not Found 1" class="illustrations_image">
<style type="text/css">
.st0_404-page-not-found-1-77 { fill: var(--primary); }
.st1_404-page-not-found-1-77 { fill: var(--secondary);}
.st2_404-page-not-found-1-77{fill:var(--tertiary);}
.st3_404-page-not-found-1-77{fill:var(--secondary);}
</style>
<g id="Numbers_404-page-not-found-1-77">
<path class="st0_404-page-not-found-1-77 targetColor" d="M107.9,303.8H39.1v-27.2l65.2-101.8h31.1v104.3h23.6v24.7h-23.6v32.9h-27.4V303.8z M107.9,213.7h-0.5L67,279.1&#xA;&#9;&#9;h40.9V213.7z" style="fill: rgb(104, 225, 253);"/>
<path class="st0_404-page-not-found-1-77 targetColor" d="M409.9,303.8h-68.8v-27.2l65.2-101.8h31.1v104.3h23.6v24.7h-23.6v32.9h-27.4V303.8z M414.7,213.7h-0.5&#xA;&#9;&#9;l-40.5,65.4h40.9L414.7,213.7z" style="fill: rgb(104, 225, 253);"/>
</g>
<g id="Chraracter_404-page-not-found-1-77">
<path class="st1_404-page-not-found-1-77" d="M245.1,182.9c-40.2,0-72.7,32.5-72.8,72.7c0,40.2,32.5,72.7,72.7,72.8s72.7-32.5,72.8-72.7c0,0,0,0,0,0&#xA;&#9;&#9;C317.8,215.5,285.2,182.9,245.1,182.9z"/>
<path class="st2_404-page-not-found-1-77" d="M245.1,329.1c-40.6,0-73.5-32.9-73.5-73.4s32.9-73.5,73.4-73.5s73.5,32.9,73.5,73.4c0,0,0,0,0,0&#xA;&#9;&#9;C318.5,296.2,285.6,329,245.1,329.1z M245.1,183.6c-39.8,0-72,32.2-72,72s32.2,72,72,72s72-32.2,72-72l0,0&#xA;&#9;&#9;C317,215.9,284.8,183.7,245.1,183.6L245.1,183.6z"/>
<ellipse class="st2_404-page-not-found-1-77" cx="283.2" cy="232" rx="3.4" ry="5"/>
<path class="st2_404-page-not-found-1-77" d="M159.6,234.1l170.8-37.8l-33.6-9.8c0,0-18.3-30.2-23.1-30.8S180.6,174,180.6,174l-5.6,42.5L159.6,234.1z"/>
<path class="st2_404-page-not-found-1-77" d="M279.2,286.5c-0.3,0-0.5-0.1-0.6-0.4c-18.5-31.4-61.1-14.8-61.5-14.7c-0.4,0.1-0.8,0-0.9-0.4&#xA;&#9;&#9;c-0.1-0.4,0-0.8,0.4-0.9c0.4-0.2,44.2-17.2,63.3,15.3c0.2,0.3,0.1,0.8-0.2,1c0,0,0,0,0,0C279.4,286.5,279.3,286.5,279.2,286.5z"/>
<polygon class="st2_404-page-not-found-1-77" points="195.2,336.4 175.9,305.9 260.3,291.5 266.1,320.7 274.7,293.8 317.8,302.3 309,336.4 &#9;"/>
<path class="st1_404-page-not-found-1-77" d="M303.4,270.3l7.8-1.1l0,0l4,29c0.3,2-1.1,3.9-3.2,4.2h0l-0.5,0.1c-2,0.3-3.9-1.1-4.2-3.2l0,0L303.4,270.3&#xA;&#9;&#9;L303.4,270.3z"/>
<path class="st2_404-page-not-found-1-77" d="M311,303.2c-2.2,0-4.1-1.6-4.4-3.8l-4-29c0-0.4,0.2-0.7,0.6-0.8l7.8-1.1c0.2,0,0.4,0,0.5,0.1&#xA;&#9;&#9;c0.1,0.1,0.2,0.3,0.3,0.5l4,29c0.4,2.4-1.3,4.6-3.7,5c0,0,0,0,0,0l-0.5,0.1C311.4,303.2,311.2,303.2,311,303.2z M304.1,270.9&#xA;&#9;&#9;l3.9,28.4c0.2,1.6,1.7,2.8,3.4,2.6l0.5-0.1l0,0c1.6-0.2,2.8-1.7,2.6-3.4l-3.9-28.3L304.1,270.9z"/>
<rect x="299.3" y="243.6" transform="matrix(0.9906 -0.1371 0.1371 0.9906 -32.4796 44.3355)" class="st1_404-page-not-found-1-77" width="12.7" height="28.7"/>
<path class="st2_404-page-not-found-1-77" d="M301.3,273.7c-0.2,0-0.3,0-0.4-0.1c-0.1-0.1-0.2-0.3-0.3-0.5l-3.9-28.4c0-0.2,0-0.4,0.1-0.5&#xA;&#9;&#9;c0.1-0.1,0.3-0.2,0.5-0.3l12.5-1.7c0.4,0,0.7,0.2,0.8,0.6l3.9,28.4c0,0.4-0.2,0.7-0.6,0.8L301.3,273.7L301.3,273.7z M298.2,245.2&#xA;&#9;&#9;l3.7,27l11.2-1.5l-3.7-27L298.2,245.2z"/>
<path class="st1_404-page-not-found-1-77" d="M308.5,292.1l3.7-0.5c2.4-0.3,4.7,1.4,5,3.8l5.6,40.5c0.3,2.4-1.4,4.7-3.8,5l-3.7,0.5c-2.4,0.3-4.7-1.4-5-3.8&#xA;&#9;&#9;l-5.6-40.5C304.3,294.7,306,292.4,308.5,292.1z"/>
<path class="st2_404-page-not-found-1-77" d="M314.7,342.2c-2.6,0-4.8-1.9-5.1-4.5l-5.6-40.5c-0.4-2.8,1.6-5.4,4.4-5.8l3.7-0.5c1.4-0.2,2.7,0.2,3.8,1&#xA;&#9;&#9;c1.1,0.8,1.8,2.1,2,3.4l5.6,40.5c0.4,2.8-1.6,5.4-4.4,5.8l0,0l-3.7,0.5C315.1,342.2,314.9,342.2,314.7,342.2z M319,341L319,341z&#xA;&#9;&#9; M312.8,292.3c-0.2,0-0.3,0-0.5,0l-3.7,0.5c-2.1,0.3-3.5,2.2-3.2,4.3l5.6,40.5c0.3,2.1,2.2,3.5,4.3,3.2l3.7-0.5&#xA;&#9;&#9;c2.1-0.3,3.5-2.2,3.2-4.2c0,0,0,0,0,0l-5.6-40.5c-0.2-1-0.7-1.9-1.5-2.5C314.4,292.5,313.6,292.3,312.8,292.3L312.8,292.3z"/>
<circle class="st1_404-page-not-found-1-77" cx="302.6" cy="236.5" r="33.7"/>
<path class="st2_404-page-not-found-1-77" d="M302.6,270.9c-19,0-34.3-15.4-34.3-34.4s15.4-34.3,34.4-34.3c17.1,0,31.7,12.7,34,29.6l0,0&#xA;&#9;&#9;c2.6,18.8-10.6,36.2-29.4,38.7C305.7,270.8,304.2,270.9,302.6,270.9z M302.7,203.6c-1.5,0-3,0.1-4.5,0.3&#xA;&#9;&#9;c-18,2.5-30.6,19.1-28.1,37.2c2.5,18,19.1,30.6,37.2,28.1s30.6-19.1,28.1-37.2c0,0,0,0,0,0l0,0C333,215.7,319.1,203.6,302.7,203.6&#xA;&#9;&#9;L302.7,203.6z"/>
<circle class="st1_404-page-not-found-1-77" cx="302.6" cy="236.5" r="27.3"/>
<path class="st2_404-page-not-found-1-77" d="M302.6,264.5c-15.5,0.2-28.2-12.1-28.4-27.6c-0.2-14.3,10.4-26.5,24.6-28.2c15.3-2.1,29.5,8.6,31.6,23.9&#xA;&#9;&#9;c2.1,15.3-8.6,29.5-23.9,31.6l0,0C305.2,264.5,303.9,264.5,302.6,264.5z M302.7,209.9c-1.2,0-2.5,0.1-3.7,0.2&#xA;&#9;&#9;c-14.6,2-24.8,15.5-22.7,30c2,14.6,15.5,24.8,30,22.7c14.6-2,24.8-15.5,22.7-30c-1-7-4.7-13.3-10.3-17.6&#xA;&#9;&#9;C314.1,211.8,308.5,209.9,302.7,209.9z"/>
<path class="st1_404-page-not-found-1-77" d="M334.8,312.4c-1.8-2.4-4.2-4.3-6.9-5.7c-8-4.1-17.2-4.1-25.7-1.4c-3,1-8.8,2.3-9.1,6.3c0,1.6,1.1,3,2.7,3.5&#xA;&#9;&#9;c1.5,0.4,3.1,0.5,4.7,0.2c-2.2,0.1-4.5,0.3-6.3,1.6s-2.7,4-1.3,5.7c0.7,0.7,1.5,1.2,2.4,1.4c2,0.6,4.2,0.5,6.2-0.1&#xA;&#9;&#9;c-2.2,0.4-4.3,1.1-6.4,2c-1.2,0.5-2.6,1.5-2.5,2.9s1.4,1.9,2.5,2.3c2.4,0.8,4.9,1.3,7.5,1.4c-2.2,0.6-4.4,1.2-6.1,2.6&#xA;&#9;&#9;c-2.7,2.3-2.8,6.8,1,8.1c1.2,0.3,2.5,0.5,3.8,0.4c11.2,0,24.6-1.4,32.2-10.7c3.6-4.4,5.3-10.5,3.6-15.8&#xA;&#9;&#9;C336.7,315.3,335.8,313.7,334.8,312.4z"/>
<path class="st2_404-page-not-found-1-77" d="M301.3,344.3c-1.3,0.1-2.6-0.1-3.9-0.5c-1.7-0.5-3-1.9-3.3-3.7c-0.3-2.1,0.5-4.2,2.1-5.6&#xA;&#9;&#9;c0.9-0.7,1.9-1.3,3-1.8c-1.4-0.2-2.8-0.6-4.2-1.1c-1.9-0.6-2.9-1.6-3-2.9s0.9-2.7,2.9-3.6l1-0.4c-0.2,0-0.5-0.1-0.8-0.2&#xA;&#9;&#9;c-1.1-0.2-2.1-0.8-2.8-1.6c-0.7-0.8-1-1.9-0.8-3c0.2-1.5,1-2.9,2.3-3.8c0.4-0.3,0.9-0.5,1.4-0.8c-1.6-0.7-2.7-2.2-2.7-4&#xA;&#9;&#9;c0.3-4.1,5.4-5.6,8.7-6.6l0.9-0.3c9.4-3,18.6-2.4,26.2,1.5c2.8,1.4,5.2,3.4,7.1,5.9c1.1,1.5,2,3.1,2.5,4.9&#xA;&#9;&#9;c1.7,5.3,0.2,11.6-3.7,16.5C326.2,343,312.5,344.3,301.3,344.3L301.3,344.3z M300,325c-1.6,0.4-3.1,0.9-4.5,1.6&#xA;&#9;&#9;c-0.6,0.3-2.2,1.1-2,2.1c0.1,0.8,1.2,1.4,2.1,1.7c2.3,0.8,4.8,1.3,7.3,1.4c0.4,0,0.7,0.3,0.7,0.7c0,0.3-0.2,0.6-0.5,0.7&#xA;&#9;&#9;c-2,0.5-4.2,1.1-5.8,2.5c-1.3,1-1.9,2.7-1.6,4.3c0.2,1.3,1.2,2.3,2.4,2.6c1.1,0.3,2.3,0.5,3.5,0.4c10.8,0,24.1-1.2,31.7-10.5&#xA;&#9;&#9;c3.7-4.5,5-10.3,3.5-15.2c-0.5-1.6-1.3-3.1-2.3-4.4l0,0c-1.8-2.3-4-4.2-6.6-5.5c-7.2-3.7-16.1-4.2-25.1-1.4l-0.9,0.3&#xA;&#9;&#9;c-3,0.9-7.5,2.2-7.7,5.4c0.1,1.3,0.9,2.4,2.1,2.7c0.8,0.3,1.7,0.4,2.5,0.3c0.7-0.1,1.3-0.1,1.9-0.1c0.4,0,0.7,0.3,0.7,0.6&#xA;&#9;&#9;c0,0.4-0.2,0.7-0.6,0.8c-0.6,0.1-1.3,0.1-2,0.2c-1.4,0.1-2.8,0.5-4,1.3c-0.9,0.7-1.5,1.7-1.7,2.8c-0.1,0.7,0.1,1.4,0.5,1.9&#xA;&#9;&#9;c0.6,0.6,1.3,1,2.1,1.1c1.4,0.4,2.8,0.5,4.2,0.3c0.6-0.1,1.1-0.3,1.7-0.4c0.4-0.1,0.7,0.2,0.8,0.5c0.1,0.4-0.1,0.7-0.5,0.8&#xA;&#9;&#9;C301.1,324.8,300.6,324.9,300,325z"/>
<path class="st2_404-page-not-found-1-77" d="M261.6,263.9c-8.6,0-9.9-2.4-10.1-2.9c-0.1-0.4,0.1-0.8,0.5-0.9s0.8,0.1,0.9,0.5l0,0c0,0,1.2,2.2,10.5,1.8&#xA;&#9;&#9;c0.9,0.1,1.8-0.4,2.3-1.2c2.2-4.3-6.2-18.6-9.6-23.7c-0.2-0.3-0.1-0.8,0.2-1c0.3-0.2,0.8-0.1,1,0.2c0,0,0,0,0,0&#xA;&#9;&#9;c1.3,2,12.7,19.3,9.7,25.2c-0.7,1.3-2.1,2.1-3.5,2C262.8,263.8,262.2,263.9,261.6,263.9z"/>
<ellipse class="st3_404-page-not-found-1-77" cx="304.2" cy="235.7" rx="6.2" ry="10.6"/>
<ellipse class="st2_404-page-not-found-1-77" cx="228.5" cy="235.7" rx="4.7" ry="8.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -0,0 +1,27 @@
---
import { Image } from "@astrojs/image/components";
import type { Author } from "@/types/payload-types";
interface Props {
author: Author;
}
const { author } = Astro.props;
---
{ (author) &&
<div class="flex gap-6 border-t-2 border-secondary py-4 text-secondary">
{(typeof author.avatar === 'object') &&
<Image
src={author.avatar.url || ""}
width={150}
height={150}
aspectRatio={1}
alt={author.avatar.alt || ""}
/>
}
<div class="flex flex-col">
<h3 class="font-semibold text-base">{author.name}</h3>
<p class="text-sm font-light">{author.bio}</p>
</div>
</div>
}

View File

@ -1,5 +1,10 @@
---
import { Image } from "@astrojs/image/components";
import type { Post } from "@/types/payload-types";
interface Props {
post: Post;
}
const { post } = Astro.props;
---

View File

@ -1,5 +1,10 @@
---
import Post from "@/components/Post.astro"
import PostEntry from "@/components/PostEntry.astro"
import type { Post } from "@/types/payload-types";
interface Props {
posts: Post[];
}
const { posts } = Astro.props;
---
@ -10,8 +15,9 @@ const { posts } = Astro.props;
{
posts.length > 0 ? (
posts.map((post) => (
<Post post={post}/>
))
typeof(post) === 'object') &&
<PostEntry post={post}/>
)
) : (
<p>No posts available</p>
)

View File

@ -22,10 +22,7 @@
.dark {
--black: 10, 16, 19;:root {
}
--black: 10, 16, 19;
--white: 243, 252, 248;
--absolute-black: 0, 0, 0;
--absolute-white: 255, 255, 255;
@ -81,7 +78,7 @@
}
h4, h5, h6 {
@apply text-xl font-semibold;
@apply text-2xl font-medium;
}
p, span, li, a {

18
astro/src/pages/404.astro Normal file
View File

@ -0,0 +1,18 @@
---
import ContentLayout from "@/layouts/ContentLayout.astro"
import { Image } from "@astrojs/image/components";
import notFound from "@/assets/404.svg"
---
<ContentLayout>
<div class="flex flex-col justify-center items-center gap-8">
<Image
src={notFound}
width={360}
aspectRatio={1}
format="svg"
alt={"404"}
/>
<h1>404: Page not found</h1>
</div>
</ContentLayout>

View File

@ -1,8 +1,9 @@
---
import ContentLayout from "@/layouts/ContentLayout.astro";
import Content from "@/components/Content.astro";
import type { Post } from "@/types";
import type { Post } from "@/types/payload-types";
import { getPost, getPosts } from "@/utils/payload";
import Author from "@/components/Author.astro";
export async function getStaticPaths() {
const posts = await getPosts();
@ -18,11 +19,15 @@ const post = id && (await getPost(id));
{
post ? (
<ContentLayout title={`Paystro | ${post.title!}`}>
<ContentLayout title={`${post.title!}`}>
<article class="space-y-3 my-3 max-w-prose">
<h1 class="">{post.title}</h1>
{post.content && <Content content={post.content} />}
</article>
{typeof post.author === 'object' &&
<aside class="mt-8">
<Author author={post.author} />
</aside>}
</ContentLayout>
) : (
<div>404</div>

View File

@ -1,4 +1,4 @@
import type { Post } from "@/types";
import type { Post } from "@/types/payload-types";
const url = import.meta.env.DEV
? "http://payload:3001"

View File

@ -4,7 +4,8 @@
"types": ["@astrojs/image/client"],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
"@/*": ["src/*"],
"@/types/*": ["../*"]
}
}
}

View File

@ -4,7 +4,9 @@
"license": "AGPL-3.0-or-later",
"version": "1.2",
"scripts": {
"dev": "docker compose up --build",
"dev": "yarn dev:docker & yarn payload:types",
"dev:docker": "docker compose up --build",
"payload:types": "yarn --cwd ./payload run generate:types:listen",
"stop": "docker compose down",
"dev:nobuild": "docker compose up"
}

66
payload-types.ts Normal file
View File

@ -0,0 +1,66 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:types` to regenerate this file.
*/
export interface Config {
collections: {
posts: Post;
users: User;
authors: Author;
media: Media;
};
globals: {};
}
export interface Post {
id: string;
title: string;
summary?: string;
publishedDate?: string;
thumbnail: string | Media;
content?: {
[k: string]: unknown;
}[];
author?: string | Author;
status: 'draft' | 'published' | 'archived';
updatedAt: string;
createdAt: string;
}
export interface Media {
id: string;
alt: string;
updatedAt: string;
createdAt: string;
url?: string;
filename?: string;
mimeType?: string;
filesize?: number;
width?: number;
height?: number;
}
export interface Author {
id: string;
avatar: string | Media;
name: string;
bio?: string;
user?: string | User;
updatedAt: string;
createdAt: string;
}
export interface User {
id: string;
roles: ('ssg' | 'admin' | 'editor' | 'user')[];
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string;
resetPasswordExpiration?: string;
salt?: string;
hash?: string;
loginAttempts?: number;
lockUntil?: string;
password?: string;
}

View File

@ -6,17 +6,22 @@
"license": "MIT",
"scripts": {
"dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts node -r tsconfig-paths/register node_modules/payload/dist/bin/index.js build",
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts node -r tsconfig-paths/register node_modules/payload/dist/bin/index.js build ",
"build:server": "tsc",
"build": "yarn build:payload && yarn build:server",
"build": "yarn generate:types && yarn build:payload && yarn build:server",
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js node -r tsconfig-paths/register dist/server.js",
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts node -r tsconfig-paths/register node_modules/payload/dist/bin/index.js generate:types"
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts node -r tsconfig-paths/register node_modules/payload/dist/bin/index.js generate:types",
"generate:types:listen": "nodemon --watch src --ext ts --exec 'npm run generate:types'"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.6",
"@payloadcms/db-mongodb": "^1.5.1",
"@payloadcms/plugin-cloud": "^3.0.1",
"@payloadcms/richtext-slate": "^1.5.2",
"cross-env": "^7.0.3",
"dotenv": "^16.3.1",
"express": "^4.17.1",
"payload": "^1.15.6",
"payload": "^2.18.3",
"tsconfig-paths": "^4.2.0"
},
"devDependencies": {

View File

@ -0,0 +1,12 @@
import { Access, FieldAccess } from "payload/types";
import { User } from "@/types/payload-types";
export const isAdmin: Access<any, User> = ({ req: { user } }) => {
// Return true or false based on if the user has an admin role
return Boolean(user?.roles?.includes('admin'));
}
export const isAdminFieldLevel: FieldAccess<{ id: string }, unknown, User> = ({ req: { user } }) => {
// Return true or false based on if the user has an admin role
return Boolean(user?.roles?.includes('admin'));
}

View File

@ -0,0 +1,21 @@
import { Access } from "payload/config";
export const isAdminOrSelf: Access = ({ req: { user } }) => {
// Need to be logged in
if (user) {
// If user has role of 'admin'
if (user.roles?.includes('admin')) {
return true;
}
// If any other type of user, only provide access to themselves
return {
id: {
equals: user.id,
}
}
}
// Reject everyone else
return false;
}

View File

@ -0,0 +1,12 @@
import { Access, FieldAccess } from "payload/types";
import { User } from "@/types/payload-types";
export const isEditor: Access<any, User> = ({ req: { user } }) => {
// Return true or false based on if the user has an editor role
return Boolean(user?.roles?.some(role => ['user', 'editor', 'admin'].includes(role)));
}
export const isEditorFieldLevel: FieldAccess<{ id: string }, unknown, User> = ({ req: { user } }) => {
// Return true or false based on if the user has an editor role
return Boolean(user?.roles?.some(role => ['user', 'editor', 'admin'].includes(role)));
}

View File

@ -0,0 +1,12 @@
import { Access, FieldAccess } from "payload/types";
import { User } from "@/types/payload-types";
export const isSSG: Access<any, User> = ({ req: { user } }) => {
// Return true or false based on if the user has an ssg or admin role
return Boolean(user?.roles?.some(role => ['ssg', 'admin'].includes(role)));
}
export const isSSGFieldLevel: FieldAccess<{ id: string }, unknown, User> = ({ req: { user } }) => {
// Return true or false based on if the user has an ssg or admin role
return Boolean(user?.roles?.some(role => ['ssg', 'admin'].includes(role)));
}

View File

@ -0,0 +1,13 @@
import { Access, FieldAccess } from "payload/types";
import { User } from "@/types/payload-types";
export const isUser: Access<any, User> = ({ req: { user } }) => {
// Return true or false based on if the user has an ssg or admin role
return Boolean(user?.roles?.some(role => ['user', 'editor', 'admin'].includes(role)));
}
export const isUserFieldLevel: FieldAccess<{ id: string }, unknown, User> = ({ req: { user } }) => {
// Return true or false based on if the user has an ssg or admin role
return Boolean(user?.roles?.some(role => ['user', 'editor', 'admin'].includes(role)));
}

View File

@ -0,0 +1,49 @@
import { CollectionConfig } from "payload/types";
import { isAdmin } from "@/access/isAdmin";
import { isEditor } from "@/access/isEditor";
import { isSSG } from "@/access/isSSG";
import { isUser } from "@/access/isUser";
const Authors: CollectionConfig = {
slug: "authors",
admin: {
defaultColumns: ["name"],
useAsTitle: "name",
},
access: {
//TODO: Author can CRUD own post
create: isUser,
read: () => true,
update: isEditor,
delete: isEditor,
},
fields: [
{
name: "avatar",
type: "upload",
relationTo: "media",
required: true,
},
{
name: "name",
type: "text",
required: true
},
{
name: "bio",
type: "text",
required: false,
},
{
name: "user",
type: "upload",
relationTo: "users",
admin: {
description: 'The selected user will be able to edit this author'
},
}
],
};
export default Authors;

View File

@ -1,3 +1,4 @@
import { isUser } from "@/access/isUser";
import { CollectionConfig } from "payload/types";
export const Media: CollectionConfig = {
@ -5,15 +6,15 @@ export const Media: CollectionConfig = {
admin: {},
access: {
read: (): boolean => true,
create: () => true,
update: () => true,
create: isUser,
update: isUser,
delete: isUser,
},
upload: {
staticURL: "/media",
staticDir: "media",
mimeTypes: ["image/*"],
},
fields: [
{
name: "alt",

View File

@ -1,4 +1,10 @@
import { CollectionConfig } from "payload/types";
import { isAdmin } from "@/access/isAdmin";
import { isEditor } from "@/access/isEditor";
import { isSSG } from "@/access/isSSG";
import { isUser } from "@/access/isUser";
import { slateEditor } from '@payloadcms/richtext-slate'
const Posts: CollectionConfig = {
slug: "posts",
versions: true,
@ -7,9 +13,11 @@ const Posts: CollectionConfig = {
useAsTitle: "title",
},
access: {
//TODO: Author can CRUD own post
create: isUser,
read: () => true,
create: () => true,
update: () => true,
update: isEditor,
delete: isEditor,
},
hooks: {
afterChange: [
@ -61,33 +69,34 @@ const Posts: CollectionConfig = {
required: true,
},
{
name: "content",
type: "richText",
admin: {
elements: ["h2", "h3", "h4", "link", "ol", "ul", "upload"],
leaves: ["bold", "italic", "underline"],
upload: {
collections: {
media: {
fields: [
{
name: "image",
type: "upload",
relationTo: "media",
required: true,
},
],
name: 'content',
type: 'richText',
editor: slateEditor({
admin: {
elements: ["h2", "h3", "h4", "link", "ol", "ul", "upload", "blockquote", "indent"],
leaves: ["bold", "italic", "underline", "strikethrough"],
upload: {
collections: {
media: {
fields: [
{
name: "image",
type: "upload",
relationTo: "media",
required: true,
},
],
},
},
},
},
},
})
},
{
name: "author",
//TODO: Add active user as default
type: 'relationship',
relationTo: 'users',
hasMany: false,
required: false,
relationTo: 'authors',
admin: {
position: "sidebar",
},
@ -95,6 +104,7 @@ const Posts: CollectionConfig = {
{
name: "status",
type: "select",
required: true,
options: [
{
value: "draft",

View File

@ -1,20 +1,41 @@
import { CollectionConfig } from 'payload/types';
import { isAdmin, isAdminFieldLevel } from '../access/isAdmin';
import { isAdminOrSelf } from '../access/isAdminOrSelf';
const Users: CollectionConfig = {
slug: 'users',
auth: true,
admin: {
defaultColumns: ["roles", "email"],
useAsTitle: 'email',
},
access: {
read: () => true,
create: isAdmin,
read: isAdminOrSelf,
update: isAdminOrSelf,
delete: isAdmin,
},
fields: [
// Email added by default
fields: [
{
name: 'name',
type: 'text',
}
name: 'roles',
type: 'select',
options: [
{ label: 'ssg', value: 'ssg' }, //cRud
{ label: 'Admin', value: 'admin' }, //CRUD, role creation
{ label: 'Editor', value: 'editor' }, //CRUD
{ label: 'User', value: 'user' }, //cRud, CRUD own entries
],
required: true,
defaultValue: "user",
// JWT so that role is accessible from 'req.user'
saveToJWT: true,
hasMany: true,
access: {
create: isAdminFieldLevel,
read: () => true,
update: isAdminFieldLevel,
},
},
],
};

View File

@ -2,12 +2,19 @@ import { buildConfig } from "payload/config";
import path from "path";
import Posts from "@/collections/Posts";
import Users from "@/collections/Users";
import Authors from "./collections/Authors";
import Media from "@/collections/Media";
import { payloadCloud } from '@payloadcms/plugin-cloud'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { webpackBundler } from '@payloadcms/bundler-webpack'
import { slateEditor } from '@payloadcms/richtext-slate'
export default buildConfig({
serverURL: process.env.PAYLOAD_URL,
admin: {
user: Users.slug,
bundler: webpackBundler(),
webpack: (config) => ({
...config,
resolve: {
@ -19,8 +26,13 @@ export default buildConfig({
},
}),
},
collections: [Posts, Users, Media],
collections: [Posts, Users, Authors, Media],
typescript: {
outputFile: path.resolve("/", "types.ts"),
outputFile: path.resolve("../", "payload-types.ts"),
},
});
plugins: [payloadCloud()],
editor: slateEditor({}),
db: mongooseAdapter({
url: process.env.MONGODB_URI,
}),
});

17
payload/src/seed.ts Normal file
View File

@ -0,0 +1,17 @@
import { Payload } from "payload";
import { User } from "@/types/payload-types";
export const seed = async (payload: Payload): Promise<void> => {
// Local API methods skip all access control by default
// so we can easily create an admin user directly in init
/* Disable to prevent mistakes */
/* await payload.create<User>({
collection: 'users',
data: {
email: 'astro@ssg.js',
password: 'password',
roles: ['ssg']
}
}) */
}

View File

@ -10,7 +10,7 @@ app.get("/", (_, res) => {
payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
//mongoURL: process.env.MONGODB_URI,
express: app,
onInit: () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);

View File

@ -7,7 +7,8 @@
"skipLibCheck": true,
"outDir": "./dist",
"paths": {
"@/*": ["./src/*", "./dist/*", "./dist/src/*"]
"@/*": ["./src/*", "./dist/*", "./dist/src/*"],
"@/types/*": ["../*"]
},
"jsx": "react"
},

File diff suppressed because it is too large Load Diff