generated from autonomic-cooperative/astro-payload-template
Compare commits
63 Commits
da435c8fc1
...
t-work
Author | SHA1 | Date | |
---|---|---|---|
f37a1719b7 | |||
d62e7d788e | |||
8fdfb2fbe4 | |||
0bb0644b55 | |||
b85c62f3fc | |||
910f943a2d | |||
10cb01964b | |||
91073bd498 | |||
cc0a5cb1c5 | |||
7dbc81c5b2 | |||
4b30d58db6 | |||
62e50496a2 | |||
2a494425ad | |||
b91dd893a4 | |||
691729c53c | |||
c90d2c0d9b | |||
4544c7942c | |||
7997268fad | |||
d8cfd644f8 | |||
09ad9bdc6d | |||
f94bc5d822 | |||
370eac2b25 | |||
ba6ca90c3a | |||
985774bf24 | |||
747ada89e9 | |||
ff564a62ec | |||
9629c93ceb | |||
d7f22fdd5f | |||
1ccf660f5b | |||
1dbb075cd8 | |||
386b5c17f9 | |||
3aa5ec6a69 | |||
db712ecffc | |||
e1a782ad39 | |||
b8b10670f9 | |||
b83ce8c97a | |||
0a7b0e98b2 | |||
f017fa28dc | |||
40d257a0eb | |||
200bfcce2a | |||
e3f725bf5c | |||
526bd24742 | |||
2cbd830255 | |||
ad7480d13a | |||
0a2dde8009 | |||
523b7c9fd7 | |||
e99eb97462 | |||
6d213b7644 | |||
e20afa3313 | |||
d30cd24f54 | |||
a87bff676b | |||
5d84e7b38d | |||
aa7a5acf94 | |||
b1e2c8d30b | |||
7d2f0822fa | |||
456db37eb6 | |||
36b5ab7cd2 | |||
03c14f0ca7 | |||
421394a794 | |||
a8362fa88f | |||
467b4bb0ad | |||
7234cef7e1 | |||
7ba1f78721 |
57
.drone.yml
57
.drone.yml
@ -4,7 +4,7 @@ name: publish pipeline
|
||||
steps:
|
||||
- name: publish astro container
|
||||
image: plugins/docker
|
||||
settings:
|
||||
settings: &docker-build-settings
|
||||
username: 3wordchant
|
||||
password:
|
||||
from_secret: git_autonomic_zone_token_3wc
|
||||
@ -14,19 +14,35 @@ steps:
|
||||
registry: git.autonomic.zone
|
||||
context: astro
|
||||
dockerfile: astro/Dockerfile
|
||||
- name: publish payload container
|
||||
when: &exclude-event-custom
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
exclude:
|
||||
- custom
|
||||
|
||||
- name: publish payload dev container
|
||||
image: plugins/docker
|
||||
settings:
|
||||
username: 3wordchant
|
||||
password:
|
||||
from_secret: git_autonomic_zone_token_3wc
|
||||
settings: &payload-build-settings
|
||||
<<: *docker-build-settings
|
||||
# NOTE: edit this if you want your image called something else
|
||||
repo: git.autonomic.zone/autonomic-cooperative/astro-payload-test-payload
|
||||
auto_tag: true
|
||||
registry: git.autonomic.zone
|
||||
repo: git.autonomic.zone/autonomic-cooperative/astro-payload-test-payload-dev
|
||||
context: payload
|
||||
dockerfile: payload/Dockerfile
|
||||
target: dev
|
||||
when:
|
||||
<<: *exclude-event-custom
|
||||
|
||||
- name: publish payload prod container
|
||||
image: plugins/docker
|
||||
settings:
|
||||
<<: *payload-build-settings
|
||||
username: 3wordchant
|
||||
repo: git.autonomic.zone/autonomic-cooperative/astro-payload-test-payload
|
||||
target: prod
|
||||
when:
|
||||
<<: *exclude-event-custom
|
||||
|
||||
- name: deploy stack
|
||||
image: git.coopcloud.tech/coop-cloud/stack-ssh-deploy:latest
|
||||
settings:
|
||||
@ -46,18 +62,10 @@ steps:
|
||||
DRONE_URL: "https://drone.autonomic.zone"
|
||||
PAYLOAD_URL: "https://admin.paystro.swarm-demo.autonomic.zone"
|
||||
depends_on:
|
||||
- publish payload container
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
exclude:
|
||||
- custom
|
||||
---
|
||||
kind: pipeline
|
||||
name: build astro
|
||||
- publish payload prod container
|
||||
when:
|
||||
<<: *exclude-event-custom
|
||||
|
||||
steps:
|
||||
- name: build astro content
|
||||
image: git.autonomic.zone/autonomic-cooperative/astro-payload-test-astro:latest
|
||||
environment:
|
||||
@ -66,6 +74,9 @@ steps:
|
||||
- cd astro
|
||||
- mv /base/node_modules .
|
||||
- yarn build
|
||||
depends_on:
|
||||
- deploy stack
|
||||
|
||||
- name: copy built content to stack
|
||||
image: git.coopcloud.tech/coop-cloud/docker-cp-deploy:latest
|
||||
settings:
|
||||
@ -77,7 +88,5 @@ steps:
|
||||
dest: /usr/share/nginx/html/
|
||||
deploy_key:
|
||||
from_secret: drone_ssh_swarm-demo.autonomic.zone
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- custom
|
||||
depends_on:
|
||||
- build astro content
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,3 +2,5 @@
|
||||
data
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
#payload-types.ts
|
44
README.md
44
README.md
@ -1,4 +1,16 @@
|
||||
# How to use this template
|
||||
# Paystro
|
||||
|
||||
Paystro is a pre-configured setup for Astro and Payloadcms, designed to make it easy for you to start building your website. With Paystro, you'll have a complete development environment that you can run locally using Docker. This setup simplifies the testing and development of your website before deploying it to a production environment.
|
||||
|
||||
## Architecture
|
||||
|
||||
Paystro is a fork of [Astroad](https://github.com/mooxl/astroad).
|
||||
|
||||
Unlike Astroad – where the "Astro" image is the built static site served with Nginx – Paystro's Astro image is a builder image.
|
||||
|
||||
In Paystro, the Docker stack just contains Payload and a generic Nginx container.
|
||||
|
||||
## How to use this template
|
||||
|
||||
1. Create a new Gitea repository based on this template repo (choose at least
|
||||
"git content")
|
||||
@ -11,24 +23,30 @@ To set up deployment:
|
||||
including adding an SSH key as an organisational secret for the organisation
|
||||
this project is in (see [Autonomic internal
|
||||
docs](https://docz.autonomic.zone/doc/setting-up-auto-deployment-using-drone-I4j2onjaKT))
|
||||
2. Edit `.drone.yml` to define the server to host on, and the
|
||||
`STACK_NAME` / `DOMAIN` of the deployed app.
|
||||
3. Generate secrets (`openssl rand -hex 32` works well) for `PAYLOAD_SECRET` and
|
||||
2. Edit `.drone.yml` to set variables:
|
||||
- `HOST`: hostname or IP address of the server to deploy to (e.g.
|
||||
`vps.example.tld`)
|
||||
- `DOMAIN`: domain name of the live site (e.g. `site.example.tld`)
|
||||
- `STACK_NAME`: the Docker stack name, usually `$DOMAIN` with dots replaced
|
||||
with underscores (e.g. `site_example_tld`)
|
||||
- `DRONE_URL`: root URL for the Drone instance (e.g.
|
||||
`https://drone.example.tld`)
|
||||
3. Make sure that DNS records for `$DOMAIN` and `admin.$DOMAIN`, pointing to
|
||||
`$HOST`, are defined.
|
||||
4. Generate secrets (`openssl rand -hex 32` works well) for `PAYLOAD_SECRET` and
|
||||
`MONGO_PASSWORD`. Insert the personal user token for a Drone bot user (e.g.
|
||||
`autono-bot`) as `TOKEN`). For all of them, use something like `echo "foobar"
|
||||
| docker secret create paystro_swarm-demo_autonomic_zone_token_v1 -`, where
|
||||
`paystro_swarm-demo_autonomic_zone` is the `STACK_NAME`.
|
||||
4. Activate the repo in Drone
|
||||
5. Activate the repo in Drone
|
||||
|
||||
# Paystro
|
||||
---
|
||||
|
||||
Paystro is a pre-configured setup for Astro and Payloadcms, designed to make it easy for you to start building your website. With Paystro, you'll have a complete development environment that you can run locally using Docker. This setup simplifies the testing and development of your website before deploying it to a production environment.
|
||||
|
||||
Paystro is a fork of [Astroad](https://github.com/mooxl/astroad).
|
||||
# ${REPO_NAME}
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before getting started with Paystro, make sure you have Docker installed (and, if your Docker doesn't include `docker compose`, the separate `docker-compose` tool).
|
||||
Before getting started, make sure you have Docker installed (and, if your Docker doesn't include `docker compose`, the separate `docker-compose` tool).
|
||||
|
||||
## Configuration
|
||||
|
||||
@ -44,8 +62,4 @@ The `docker-compose.yml` file includes everything you need to run the containers
|
||||
|
||||
Whenever the repository is updated, Drone builds new Docker images for Payload and Astro, and deploys a new Docker Swarm stack to the `HOST` configured in `.drone.yml`.
|
||||
|
||||
Unlike Astroad – where the "Astro" image is the built static site served with Nginx – Paystro's Astro image is a builder image.
|
||||
|
||||
In Paystro, the Docker stack just contains Payload and a generic Nginx container.
|
||||
|
||||
Whenever changes are made to Payload content, Drone uses the Astro builder image to regenerate the static site, and publish it to the Nginx container by copying it into the Docker volume.
|
||||
Whenever changes are made to Payload content on the live site, Drone uses the Astro builder image to regenerate the static site, and publish it to the Nginx container by copying it into the Docker volume.
|
||||
|
3
astro/.gitignore
vendored
3
astro/.gitignore
vendored
@ -16,6 +16,3 @@ pnpm-debug.log*
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# types
|
||||
src/types.ts
|
@ -9,6 +9,9 @@ export default defineConfig({
|
||||
build: {
|
||||
inlineStylesheets: "auto",
|
||||
},
|
||||
server: {
|
||||
port: 3000
|
||||
},
|
||||
integrations: [
|
||||
tailwind({
|
||||
config: {
|
||||
|
BIN
astro/public/not-found.png
Normal file
BIN
astro/public/not-found.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
36
astro/src/assets/404.svg
Normal file
36
astro/src/assets/404.svg
Normal 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
		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
		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
		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
		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
		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
		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 	"/>
|
||||
<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
		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
		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
		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
		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
		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
		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
		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
		 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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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
		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 |
27
astro/src/components/Author.astro
Normal file
27
astro/src/components/Author.astro
Normal 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>
|
||||
}
|
@ -5,23 +5,20 @@ import { getImageSrc } from "@/utils/payload";
|
||||
const { content } = Astro.props;
|
||||
const contentArray = getContentArray(content);
|
||||
---
|
||||
|
||||
<div>
|
||||
{
|
||||
contentArray.map((value) => {
|
||||
if (typeof value === "string") {
|
||||
return <article set:html={value} />;
|
||||
return <section set:html={value} />;
|
||||
} else {
|
||||
return (
|
||||
<Image
|
||||
src={getImageSrc(value.src)}
|
||||
src={getImageSrc(value.src) || "/not-found.png"}
|
||||
width={value.width}
|
||||
height={value.height}
|
||||
format="webp"
|
||||
alt="hallo"
|
||||
alt={value.alt}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
@ -1,9 +1,11 @@
|
||||
---
|
||||
import { getCurrentYear } from "@/utils/date";
|
||||
import ThemeSwitcher from "./ThemeSwitcher.astro";
|
||||
---
|
||||
|
||||
<footer>
|
||||
<footer class="flex justify-between items-center py-4">
|
||||
<p class="text-lg font-semibold">
|
||||
© {getCurrentYear()} Example Website. All rights reserved.
|
||||
© {getCurrentYear()} Autonomic. Template distributed under AGPL 3.0.
|
||||
</p>
|
||||
<ThemeSwitcher/>
|
||||
</footer>
|
@ -1,9 +1,5 @@
|
||||
---
|
||||
const links = [
|
||||
{
|
||||
label: "Home",
|
||||
link: "/home"
|
||||
},
|
||||
{
|
||||
label: "About",
|
||||
link: "/about"
|
||||
@ -22,14 +18,16 @@ const links = [
|
||||
|
||||
<header class="flex justify-between py-6">
|
||||
<div>
|
||||
<span class="font-extrabold">
|
||||
<a href="/" class="decoration-transparent">
|
||||
<span class="text-2xl font-extrabold ">
|
||||
Logo
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<nav>
|
||||
<ul class="flex gap-4 font-bold">
|
||||
<ul class="flex gap-4 font-bold list-none">
|
||||
{links.map((item, index) => (
|
||||
<li>
|
||||
<li class="decoration-transparent">
|
||||
<a href={item.link}>{item.label}</a>
|
||||
</li>
|
||||
))}
|
||||
|
43
astro/src/components/PostEntry.astro
Normal file
43
astro/src/components/PostEntry.astro
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
import { Image } from "@astrojs/image/components";
|
||||
import type { Post } from "@/types/payload-types";
|
||||
|
||||
interface Props {
|
||||
post: Post;
|
||||
}
|
||||
|
||||
const { post } = Astro.props;
|
||||
---
|
||||
<a
|
||||
class="py-4 border-secondary decoration-transparent"
|
||||
href={`/posts/${post.id}/`}
|
||||
>
|
||||
<article class="flex px-5 py-3 gap-8">
|
||||
<Image
|
||||
src={post.thumbnail.url || "/not-found"}
|
||||
height={150}
|
||||
aspectRatio={1}
|
||||
format="webp"
|
||||
alt={post.thumbnail.alt}
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-4 w-full">
|
||||
<div class="flex justify-between w-full">
|
||||
<h3 class="">{post.title}</h3>
|
||||
{post.publishedDate && (
|
||||
<p class="font-light">
|
||||
{new Date(post.publishedDate).toLocaleDateString("de-DE")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{post.summary && <p class="max-w-prose">
|
||||
{post.summary}
|
||||
</p>}
|
||||
</div>
|
||||
<!-- {post.author.name && (
|
||||
<p>
|
||||
{post.author.name}
|
||||
</p>
|
||||
)} -->
|
||||
</article>
|
||||
</a>
|
26
astro/src/components/Posts.astro
Normal file
26
astro/src/components/Posts.astro
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
import PostEntry from "@/components/PostEntry.astro"
|
||||
import type { Post } from "@/types/payload-types";
|
||||
|
||||
interface Props {
|
||||
posts: Post[];
|
||||
}
|
||||
|
||||
const { posts } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="flex flex-col">
|
||||
<h2 class="text-secondary mb-4">Posts</h2>
|
||||
<div class="border-y-2 flex flex-col divide-y-2 border-secondary divide-secondary">
|
||||
{
|
||||
posts.length > 0 ? (
|
||||
posts.map((post) => (
|
||||
typeof(post) === 'object') &&
|
||||
<PostEntry post={post}/>
|
||||
)
|
||||
) : (
|
||||
<p>No posts available</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
12
astro/src/components/ThemeSwitcher.astro
Normal file
12
astro/src/components/ThemeSwitcher.astro
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
---
|
||||
<select id="theme-selector">
|
||||
<option value="autonomic">Autonomic</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
|
||||
<script>
|
||||
import { handleThemeChange, switchTheme, initializeThemeSelector } from "@/utils/theme"
|
||||
initializeThemeSelector()
|
||||
</script>
|
107
astro/src/global.css
Normal file
107
astro/src/global.css
Normal file
@ -0,0 +1,107 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.light {
|
||||
|
||||
--black: 10, 16, 19;
|
||||
--white: 243, 252, 248;
|
||||
--absolute-black: 0, 0, 0;
|
||||
--absolute-white: 255, 255, 255;
|
||||
|
||||
/* Use these in tailwind.config.cjs */
|
||||
--primary: var(--black);
|
||||
--secondary: var(--black);
|
||||
--tertiary: var(--black);
|
||||
|
||||
--text: var(--black);
|
||||
--textInverted: var(--absolute-white);
|
||||
--link: var(--black);
|
||||
--background: var(--white);
|
||||
}
|
||||
|
||||
.dark {
|
||||
|
||||
--black: 10, 16, 19;
|
||||
--white: 243, 252, 248;
|
||||
--absolute-black: 0, 0, 0;
|
||||
--absolute-white: 255, 255, 255;
|
||||
|
||||
/* Use these in tailwind.config.cjs */
|
||||
--primary: var(--white);
|
||||
--secondary: var(--white);
|
||||
--tertiary: var(--white);
|
||||
|
||||
--text: var(--white);
|
||||
--textInverted: var(--absolute-black);
|
||||
--link: var(--white);
|
||||
--background: var(--black);
|
||||
}
|
||||
|
||||
.autonomic {
|
||||
/* Palette */
|
||||
/* RGBA instead of hex so that opacity will work */
|
||||
--absolute-white: 255, 255, 255;
|
||||
--white: 243, 252, 248;
|
||||
--shocking-pink: 253, 0, 159;
|
||||
--shamrock-green: 0, 198, 88;
|
||||
--bubblegum-pink: 255, 133, 203;
|
||||
--bright-orange: 255, 96, 21;
|
||||
|
||||
/* Use these in tailwind.config.cjs */
|
||||
--primary: var(--shocking-pink);
|
||||
--secondary: var(--shamrock-green);
|
||||
--tertiary: var(--bright-orange);
|
||||
|
||||
--text: var(--shocking-pink);
|
||||
--textInverted: var(--absolute-white);
|
||||
--link: var(--shamrock-green);
|
||||
--background: var(--white);
|
||||
}
|
||||
|
||||
@layer components {
|
||||
|
||||
:root {
|
||||
@apply ring-secondary;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-5xl font-bold;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-4xl font-semibold;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-2xl font-semibold;
|
||||
}
|
||||
|
||||
h4, h5, h6 {
|
||||
@apply text-2xl font-medium;
|
||||
}
|
||||
|
||||
p, span, li, a {
|
||||
@apply text-xl;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-link underline;
|
||||
}
|
||||
|
||||
ol {
|
||||
@apply list-decimal;
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply list-disc;
|
||||
}
|
||||
|
||||
select {
|
||||
@apply bg-background border-primary border px-2 h-8 text-lg;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply bg-background border-primary border px-2 h-8 text-lg;
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ export interface Props {
|
||||
title: string;
|
||||
}
|
||||
const { title } = Astro.props;
|
||||
import "@/global.css"
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
@ -30,7 +31,7 @@ const { title } = Astro.props;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen flex flex-col mx-auto max-w-7xl bg-gray px-6 py-8 font-plex text-gray-200">
|
||||
<body class="autonomic min-h-screen flex flex-col mx-auto max-w-7xl px-6 text-primary bg-background">
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
|
18
astro/src/pages/404.astro
Normal file
18
astro/src/pages/404.astro
Normal 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>
|
@ -1,4 +1,5 @@
|
||||
---
|
||||
import Posts from "@/components/Posts.astro";
|
||||
import ContentLayout from "@/layouts/ContentLayout.astro";
|
||||
import { getPosts } from "@/utils/payload";
|
||||
|
||||
@ -6,9 +7,9 @@ const posts = await getPosts();
|
||||
---
|
||||
|
||||
<ContentLayout title="Paystro">
|
||||
<main class="" >
|
||||
<h1 class="font-bold text-5xl">Paystro</h1>
|
||||
<p class="mt-3 text-lg">
|
||||
<main class="flex flex-col gap-4" >
|
||||
<h1 class="">Paystro</h1>
|
||||
<p class="mt-3">
|
||||
Paystro is a pre-configured setup for Astro and Payloadcms that makes it
|
||||
easy to get started with building your website. With Paystro, you'll have
|
||||
a complete development environment that you can run locally using Docker.
|
||||
@ -20,26 +21,8 @@ const posts = await getPosts();
|
||||
reverse proxy. This setup provides a secure and scalable production
|
||||
environment for your website.
|
||||
</p>
|
||||
<h2 class="mt-6 font-bold text-2xl">Posts</h2>
|
||||
<div class="flex gap-4 mt-3 flex-wrap">
|
||||
{
|
||||
posts.length > 0 ? (
|
||||
posts.map((post) => (
|
||||
<a href={`/posts/${post.id}/`}>
|
||||
<article class="text-gray bg-gray-light px-5 py-3 rounded-md shadow-md w-64 text-center hover:-translate-y-1 transition-transform">
|
||||
<h3 class="font-bold text-lg">{post.title}</h3>
|
||||
{post.publishedDate && (
|
||||
<p>
|
||||
{new Date(post.publishedDate).toLocaleDateString("de-DE")}
|
||||
</p>
|
||||
)}
|
||||
</article>
|
||||
</a>
|
||||
))
|
||||
) : (
|
||||
<p>Add Posts in Payloadcms</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<section class="mt-4">
|
||||
<Posts posts={posts}/>
|
||||
</section>
|
||||
</main>
|
||||
</CLayout>
|
||||
</ContentLayout>
|
||||
|
@ -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,13 +19,16 @@ const post = id && (await getPost(id));
|
||||
|
||||
{
|
||||
post ? (
|
||||
<ContentLayout title={`Paystro | ${post.title!}`}>
|
||||
<div class="space-y-3 my-3">
|
||||
<a href="/">BACK</a>
|
||||
<h1 class="font-bold text-5xl">{post.title}</h1>
|
||||
<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} />}
|
||||
</div>
|
||||
</CLayout>
|
||||
</article>
|
||||
{typeof post.author === 'object' &&
|
||||
<aside class="mt-8">
|
||||
<Author author={post.author} />
|
||||
</aside>}
|
||||
</ContentLayout>
|
||||
) : (
|
||||
<div>404</div>
|
||||
)
|
||||
|
@ -12,12 +12,13 @@ export const getContentArray = (content: any) => {
|
||||
src: node.value.filename,
|
||||
width: `${node.value.width}`,
|
||||
height: `${node.value.height}`,
|
||||
alt: `${node.value.alt}`,
|
||||
}),
|
||||
},
|
||||
}).replaceAll("<p></p>", "<p> </p>");
|
||||
const htmlImageArray: (
|
||||
| string
|
||||
| { src: string; width: number; height: number }
|
||||
| { src: string; width: number; height: number, alt: string }
|
||||
)[] = [];
|
||||
let lastIndex = 0;
|
||||
while (true) {
|
||||
@ -33,6 +34,7 @@ export const getContentArray = (content: any) => {
|
||||
src: imgTag.match(/src="(.*?)"/)![1],
|
||||
width: +imgTag.match(/width="(.*?)"/)![1],
|
||||
height: +imgTag.match(/height="(.*?)"/)![1],
|
||||
alt: imgTag.match(/alt="(.*?)"/)![1],
|
||||
};
|
||||
htmlImageArray.push(remainingHtml, imgObject);
|
||||
lastIndex = imgEndIndex;
|
||||
|
@ -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"
|
||||
|
21
astro/src/utils/theme.ts
Normal file
21
astro/src/utils/theme.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export function switchTheme(themeClass: string): void {
|
||||
const bodyElement = document.body;
|
||||
const classesToRemove = ['light', 'dark', 'autonomic'];
|
||||
|
||||
bodyElement.classList.remove(...classesToRemove);
|
||||
bodyElement.classList.add(themeClass);
|
||||
}
|
||||
|
||||
export function handleThemeChange(event: Event): void {
|
||||
const selectElement = event.target as HTMLSelectElement;
|
||||
const selectedTheme = selectElement.value;
|
||||
|
||||
switchTheme(selectedTheme);
|
||||
}
|
||||
|
||||
export function initializeThemeSelector(): void {
|
||||
const themeSelector = document.getElementById('theme-selector');
|
||||
if (themeSelector) {
|
||||
themeSelector.addEventListener('change', handleThemeChange);
|
||||
}
|
||||
}
|
@ -7,11 +7,13 @@ module.exports = {
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
gray: {
|
||||
DEFAULT: "#111111",
|
||||
light: "#888888",
|
||||
dark: "#222222",
|
||||
},
|
||||
primary: "rgba(var(--primary))",
|
||||
secondary: "rgba(var(--secondary))",
|
||||
tertiary: "rgba(var(--tertiary))",
|
||||
text: "rgba(var(--text))",
|
||||
textInverted: "rgba(var(--textInverted))",
|
||||
link: "rgba(var(--link))",
|
||||
background: "rgba(var(--background))",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -4,7 +4,8 @@
|
||||
"types": ["@astrojs/image/client"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
"@/*": ["src/*"],
|
||||
"@/types/*": ["../*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ services:
|
||||
- "MONGODB_PASSWORD_FILE=/run/secrets/mongo_password"
|
||||
- "TOKEN_FILE=/run/secrets/token"
|
||||
- "REPOSITORY"
|
||||
- "DRONE_URL"
|
||||
secrets:
|
||||
- mongo_password
|
||||
- payload_secret
|
||||
|
@ -19,11 +19,12 @@ services:
|
||||
- payload
|
||||
|
||||
payload:
|
||||
image: git.autonomic.zone/autonomic-cooperative/astro-payload-test-payload:latest
|
||||
image: git.autonomic.zone/autonomic-cooperative/astro-payload-test-payload-dev:latest
|
||||
container_name: ${NAME}-payload
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: payload
|
||||
target: dev
|
||||
environment:
|
||||
DEV: 1
|
||||
NAME: ${NAME}
|
||||
@ -31,15 +32,11 @@ services:
|
||||
PAYLOAD_URL: "http://localhost:${PAYLOAD_PORT}"
|
||||
PAYLOAD_PORT: ${PAYLOAD_PORT}
|
||||
PAYLOAD_SECRET: ${PAYLOAD_SECRET}
|
||||
MONGODB_USER:
|
||||
MONGODB_HOST: mongo
|
||||
MONGODB_PASSWORD:
|
||||
MONGODB_PORT: 27017
|
||||
MONGODB_URI: "mongodb://$MONGODB_USER:$MONGODB_PASSWORD@mongo:27017"
|
||||
volumes:
|
||||
- ./payload/src:/base/src
|
||||
- ./astro/src/types.ts:/types.ts
|
||||
volumes:
|
||||
- payload_media:/prod/dist/media
|
||||
# - ./astro/src/types.ts:/types.ts
|
||||
- payload_media:/base/src/media
|
||||
ports:
|
||||
- ${PAYLOAD_PORT}:${PAYLOAD_PORT}
|
||||
networks:
|
||||
|
@ -4,7 +4,10 @@
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"version": "1.2",
|
||||
"scripts": {
|
||||
"dev": "docker compose up",
|
||||
"stop": "docker compose down"
|
||||
"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
66
payload-types.ts
Normal 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;
|
||||
}
|
@ -9,7 +9,6 @@ ENV NODE_ENV=development
|
||||
EXPOSE 3001
|
||||
CMD ["yarn","dev"]
|
||||
|
||||
|
||||
FROM base AS build
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /build
|
||||
|
@ -8,15 +8,20 @@
|
||||
"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:server": "tsc",
|
||||
"build": "yarn build:payload && yarn build:server",
|
||||
"build": "yarn generate:types && 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": {
|
||||
|
12
payload/src/access/isAdmin.ts
Normal file
12
payload/src/access/isAdmin.ts
Normal 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'));
|
||||
}
|
21
payload/src/access/isAdminOrSelf.ts
Normal file
21
payload/src/access/isAdminOrSelf.ts
Normal 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;
|
||||
}
|
12
payload/src/access/isEditor.ts
Normal file
12
payload/src/access/isEditor.ts
Normal 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)));
|
||||
}
|
12
payload/src/access/isSSG.ts
Normal file
12
payload/src/access/isSSG.ts
Normal 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)));
|
||||
}
|
13
payload/src/access/isUser.ts
Normal file
13
payload/src/access/isUser.ts
Normal 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)));
|
||||
}
|
49
payload/src/collections/Authors.ts
Normal file
49
payload/src/collections/Authors.ts
Normal 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;
|
@ -1,3 +1,4 @@
|
||||
import { isUser } from "@/access/isUser";
|
||||
import { CollectionConfig } from "payload/types";
|
||||
|
||||
export const Media: CollectionConfig = {
|
||||
@ -5,19 +6,20 @@ 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",
|
||||
type: "text",
|
||||
required: true
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -1,14 +1,23 @@
|
||||
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,
|
||||
admin: {
|
||||
defaultColumns: ["title", "author", "status"],
|
||||
useAsTitle: "title",
|
||||
},
|
||||
access: {
|
||||
//TODO: Author can CRUD own post
|
||||
create: isUser,
|
||||
read: () => true,
|
||||
create: () => true,
|
||||
update: () => true,
|
||||
update: isEditor,
|
||||
delete: isEditor,
|
||||
},
|
||||
hooks: {
|
||||
afterChange: [
|
||||
@ -38,28 +47,40 @@ const Posts: CollectionConfig = {
|
||||
{
|
||||
name: "title",
|
||||
type: "text",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: "hallo",
|
||||
name: "summary",
|
||||
type: "text",
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: "publishedDate",
|
||||
type: "date",
|
||||
},
|
||||
|
||||
{
|
||||
name: "content",
|
||||
type: "richText",
|
||||
required: false,
|
||||
admin: {
|
||||
elements: ["h2", "h3", "h4", "link", "ol", "ul", "upload"],
|
||||
leaves: ["bold", "italic", "underline"],
|
||||
position: "sidebar",
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "thumbnail",
|
||||
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: "imagel",
|
||||
name: "image",
|
||||
type: "upload",
|
||||
relationTo: "media",
|
||||
required: true,
|
||||
@ -69,10 +90,21 @@ const Posts: CollectionConfig = {
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
{
|
||||
name: "author",
|
||||
//TODO: Add active user as default
|
||||
type: 'relationship',
|
||||
relationTo: 'authors',
|
||||
admin: {
|
||||
position: "sidebar",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
type: "select",
|
||||
required: true,
|
||||
options: [
|
||||
{
|
||||
value: "draft",
|
||||
@ -82,6 +114,10 @@ const Posts: CollectionConfig = {
|
||||
value: "published",
|
||||
label: "Published",
|
||||
},
|
||||
{
|
||||
value: "archived",
|
||||
label: "Archived",
|
||||
},
|
||||
],
|
||||
defaultValue: "draft",
|
||||
admin: {
|
||||
|
@ -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
|
||||
{
|
||||
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,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -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
17
payload/src/seed.ts
Normal 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']
|
||||
}
|
||||
}) */
|
||||
}
|
@ -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()}`);
|
||||
|
@ -7,7 +7,8 @@
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"@/*": ["./src/*", "./dist/*", "./dist/src/*"]
|
||||
"@/*": ["./src/*", "./dist/*", "./dist/src/*"],
|
||||
"@/types/*": ["../*"]
|
||||
},
|
||||
"jsx": "react"
|
||||
},
|
||||
|
4334
payload/yarn.lock
4334
payload/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user