generated from autonomic-cooperative/astro-payload-template
Compare commits
69 Commits
d4e0e1f994
...
deploy-exp
Author | SHA1 | Date | |
---|---|---|---|
e9fb68ec9f | |||
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 | |||
da435c8fc1 | |||
68972aba1a | |||
670300a8dc | |||
7f2710e9a2 | |||
7d071a2672 |
61
.drone.yml
61
.drone.yml
@ -4,30 +4,45 @@ name: publish pipeline
|
|||||||
steps:
|
steps:
|
||||||
- name: publish astro container
|
- name: publish astro container
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings: &docker-build-settings
|
||||||
username: 3wordchant
|
username: 3wordchant
|
||||||
password:
|
password:
|
||||||
from_secret: git_autonomic_zone_token_3wc
|
from_secret: git_autonomic_zone_token_3wc
|
||||||
# NOTE: edit this if you want your image called something else
|
# NOTE: edit this if you want your image called something else
|
||||||
repo: git.autonomic.zone/autonomic-cooperative/astro-payload-test-astro
|
repo: git.autonomic.zone/autonomic-cooperative/astro-payload-test-astro
|
||||||
auto_tag: true
|
auto_tag: true
|
||||||
registry: git.autonomic.zone
|
registry: git.autonomic.zone
|
||||||
context: astro
|
context: astro
|
||||||
dockerfile: astro/Dockerfile
|
dockerfile: astro/Dockerfile
|
||||||
- name: publish payload container
|
when: &exclude-event-custom
|
||||||
|
branch:
|
||||||
|
- main
|
||||||
|
event:
|
||||||
|
exclude:
|
||||||
|
- custom
|
||||||
|
|
||||||
|
- name: publish payload dev container
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings: &payload-build-settings
|
||||||
username: 3wordchant
|
<<: *docker-build-settings
|
||||||
password:
|
|
||||||
from_secret: git_autonomic_zone_token_3wc
|
|
||||||
# NOTE: edit this if you want your image called something else
|
# NOTE: edit this if you want your image called something else
|
||||||
repo: git.autonomic.zone/autonomic-cooperative/astro-payload-test-payload
|
repo: git.autonomic.zone/autonomic-cooperative/astro-payload-test-payload-dev
|
||||||
auto_tag: true
|
|
||||||
registry: git.autonomic.zone
|
|
||||||
context: payload
|
context: payload
|
||||||
dockerfile: payload/Dockerfile
|
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
|
target: prod
|
||||||
# See https://docz.autonomic.zone/doc/setting-up-auto-deployment-using-drone-I4j2onjaKT
|
when:
|
||||||
|
<<: *exclude-event-custom
|
||||||
|
|
||||||
- name: deploy stack
|
- name: deploy stack
|
||||||
image: git.coopcloud.tech/coop-cloud/stack-ssh-deploy:latest
|
image: git.coopcloud.tech/coop-cloud/stack-ssh-deploy:latest
|
||||||
settings:
|
settings:
|
||||||
@ -44,20 +59,13 @@ steps:
|
|||||||
SECRET_MONGO_PASSWORD_VERSION: v1
|
SECRET_MONGO_PASSWORD_VERSION: v1
|
||||||
NGINX_CONF_VERSION: v1
|
NGINX_CONF_VERSION: v1
|
||||||
REPOSITORY: "autonomic-cooperative/astro-payload-test"
|
REPOSITORY: "autonomic-cooperative/astro-payload-test"
|
||||||
|
DRONE_URL: "https://drone.autonomic.zone"
|
||||||
PAYLOAD_URL: "https://admin.paystro.swarm-demo.autonomic.zone"
|
PAYLOAD_URL: "https://admin.paystro.swarm-demo.autonomic.zone"
|
||||||
depends_on:
|
depends_on:
|
||||||
- publish payload container
|
- publish payload prod container
|
||||||
trigger:
|
when:
|
||||||
branch:
|
<<: *exclude-event-custom
|
||||||
- main
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- custom
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: build astro
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build astro content
|
- name: build astro content
|
||||||
image: git.autonomic.zone/autonomic-cooperative/astro-payload-test-astro:latest
|
image: git.autonomic.zone/autonomic-cooperative/astro-payload-test-astro:latest
|
||||||
environment:
|
environment:
|
||||||
@ -66,6 +74,9 @@ steps:
|
|||||||
- cd astro
|
- cd astro
|
||||||
- mv /base/node_modules .
|
- mv /base/node_modules .
|
||||||
- yarn build
|
- yarn build
|
||||||
|
depends_on:
|
||||||
|
- deploy stack
|
||||||
|
|
||||||
- name: copy built content to stack
|
- name: copy built content to stack
|
||||||
image: git.coopcloud.tech/coop-cloud/docker-cp-deploy:latest
|
image: git.coopcloud.tech/coop-cloud/docker-cp-deploy:latest
|
||||||
settings:
|
settings:
|
||||||
@ -77,7 +88,5 @@ steps:
|
|||||||
dest: /usr/share/nginx/html/
|
dest: /usr/share/nginx/html/
|
||||||
deploy_key:
|
deploy_key:
|
||||||
from_secret: drone_ssh_swarm-demo.autonomic.zone
|
from_secret: drone_ssh_swarm-demo.autonomic.zone
|
||||||
|
depends_on:
|
||||||
trigger:
|
- build astro content
|
||||||
event:
|
|
||||||
- custom
|
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,3 +2,5 @@
|
|||||||
data
|
data
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
|
#payload-types.ts
|
101
README.md
101
README.md
@ -1,66 +1,65 @@
|
|||||||
# Paystro
|
# 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 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")
|
||||||
|
2. Edit `.env.sample` to define the project name
|
||||||
|
3. Remove this notice (everything down to the `---`)
|
||||||
|
|
||||||
|
To set up deployment:
|
||||||
|
|
||||||
|
1. Make sure the server you want to deploy to is set up for Drone access,
|
||||||
|
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 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`.
|
||||||
|
5. Activate the repo in Drone
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# ${REPO_NAME}
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
Before getting started with Paystro, make sure you have the necessary software installed:
|
Before getting started, make sure you have Docker installed (and, if your Docker doesn't include `docker compose`, the separate `docker-compose` tool).
|
||||||
|
|
||||||
- Docker
|
|
||||||
- Node.js
|
|
||||||
- Yarn
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
While there's no configuration necessary for local development, deployment via Github Workflows requires specific secrets and variables to be set.
|
Copy `.env.sample` to `.env` and edit as appropriate (e.g. to change the port for Payload).
|
||||||
|
|
||||||
### Secrets:
|
|
||||||
|
|
||||||
- `USER`: User on the server
|
|
||||||
- `HOST`: IP or URL of the server
|
|
||||||
- `KEY`: SSH KEY for connecting to the server
|
|
||||||
- `MONGODB_PW`: Password for MongoDB
|
|
||||||
- `MONGODB_USER`: User for MongoDB
|
|
||||||
- `PATH`: Path where the repository resides on the server
|
|
||||||
- `PAYLOAD_PORT`: Port at which Payload listens
|
|
||||||
- `PAYLOAD_SECRET`: String to encrypt Payload data
|
|
||||||
- `TOKEN`: Github Access Token for the webhook to trigger the payload.yml workflow and execute a new Astro build
|
|
||||||
|
|
||||||
### Variables:
|
|
||||||
|
|
||||||
- `ASTRO_HOST`: Hostdomain of the Frontend
|
|
||||||
- `PAYLOAD_HOST`: Hostdomain of the CMS
|
|
||||||
- `PAYLOAD_URL`: URL of the CMS
|
|
||||||
- `NAME`: Name of the Container and Project
|
|
||||||
|
|
||||||
Please remember to set these secrets and variables in your repository settings to ensure a successful deployment through Github Workflows.
|
|
||||||
|
|
||||||
Once the secrets and variables are set on GitHub, they will replace the existing ones in the `.env` file on the server during deployment. This is done by the push.yml workflow, which replaces the placeholders in the `.env` with the actual secrets and variables defined in the repository settings. Please ensure that the names of your secrets and variables match with the placeholders in the `.env` file.
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
To get started with Paystro, you'll need to have Docker and NPM || Yarn || PNPM installed on your machine.
|
|
||||||
|
|
||||||
You have two options for getting the repository:
|
|
||||||
|
|
||||||
1. Use the 'Use this template' button on the Github repository. This will create a new repository in your Github account with the same directory structure and files as Paystro. After the new repository is created, you can clone it to your local machine.
|
|
||||||
1. Alternatively, you can directly clone the Paystro repository: git clone https://github.com/mooxl/paystro.git. If you choose this option, remember to change the origin of your remote repository to a new one to avoid pushing changes directly to the Paystro repository. This can be done with the command: git remote set-url origin https://github.com/USERNAME/REPOSITORY.git where USERNAME is your username and REPOSITORY is the name of your new repository.
|
|
||||||
|
|
||||||
Once you've cloned the repository or created your own from the template, follow these steps:
|
|
||||||
|
|
||||||
1. Change into the repository directory: `cd {newName}`
|
|
||||||
1. Start the containers: `yarn dev`
|
|
||||||
|
|
||||||
This will start up the Astro, Payloadcms and Mongo containers and make them available on your local machine. Astro will be served at http://localhost:3000 and the Payload will be available at http://localhost:3001.
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
The `docker-compose.yml` and `docker-compose-dev.yml` files includes everything you need to run the containers. The containers use the environment variables declared in the `.env` file and mounted volumes to store data persistently even after the containers are stopped and started.
|
Launch local containers using `yarn dev` or, if you don't have yarn installed, plain `docker-compose up` / `docker compose up`.
|
||||||
|
|
||||||
|
The `docker-compose.yml` file includes everything you need to run the containers. The containers use the environment variables declared in the `.env` file, and mounted volumes to store data persistently even after the containers are stopped and started.
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
Deployment is handled by a Github Actions Workflow on every push. It logs into the server via SSH, pulls or clones the latest version of the repository, and runs `yarn prod`.
|
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`.
|
||||||
|
|
||||||
Because Astro is completely static, a content change in the CMS must trigger a new build of Astro. Therefore, there’s a `payload.yml` workflow that gets triggered by a webhook after every content change from Payload.
|
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.
|
||||||
|
|
||||||
Ensure you have Traefik set up as a reverse proxy before deployment. The prod script will launch your site in a production-ready environment.
|
|
||||||
|
5
astro/.gitignore
vendored
5
astro/.gitignore
vendored
@ -15,7 +15,4 @@ pnpm-debug.log*
|
|||||||
|
|
||||||
|
|
||||||
# macOS-specific files
|
# macOS-specific files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# types
|
|
||||||
src/types.ts
|
|
@ -9,6 +9,9 @@ export default defineConfig({
|
|||||||
build: {
|
build: {
|
||||||
inlineStylesheets: "auto",
|
inlineStylesheets: "auto",
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
port: 3000
|
||||||
|
},
|
||||||
integrations: [
|
integrations: [
|
||||||
tailwind({
|
tailwind({
|
||||||
config: {
|
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 { content } = Astro.props;
|
||||||
const contentArray = getContentArray(content);
|
const contentArray = getContentArray(content);
|
||||||
---
|
---
|
||||||
|
|
||||||
<div>
|
|
||||||
{
|
{
|
||||||
contentArray.map((value) => {
|
contentArray.map((value) => {
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
return <article set:html={value} />;
|
return <section set:html={value} />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Image
|
<Image
|
||||||
src={getImageSrc(value.src)}
|
src={getImageSrc(value.src) || "/not-found.png"}
|
||||||
width={value.width}
|
width={value.width}
|
||||||
height={value.height}
|
height={value.height}
|
||||||
format="webp"
|
format="webp"
|
||||||
alt="hallo"
|
alt={value.alt}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</div>
|
|
@ -1,9 +1,11 @@
|
|||||||
---
|
---
|
||||||
import { getCurrentYear } from "@/utils/date";
|
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">
|
<p class="text-lg font-semibold">
|
||||||
© {getCurrentYear()} Example Website. All rights reserved.
|
© {getCurrentYear()} Autonomic. Template distributed under AGPL 3.0.
|
||||||
</p>
|
</p>
|
||||||
|
<ThemeSwitcher/>
|
||||||
</footer>
|
</footer>
|
@ -1,9 +1,5 @@
|
|||||||
---
|
---
|
||||||
const links = [
|
const links = [
|
||||||
{
|
|
||||||
label: "Home",
|
|
||||||
link: "/home"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "About",
|
label: "About",
|
||||||
link: "/about"
|
link: "/about"
|
||||||
@ -22,14 +18,16 @@ const links = [
|
|||||||
|
|
||||||
<header class="flex justify-between py-6">
|
<header class="flex justify-between py-6">
|
||||||
<div>
|
<div>
|
||||||
<span class="font-extrabold">
|
<a href="/" class="decoration-transparent">
|
||||||
Logo
|
<span class="text-2xl font-extrabold ">
|
||||||
</span>
|
Logo
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<nav>
|
<nav>
|
||||||
<ul class="flex gap-4 font-bold">
|
<ul class="flex gap-4 font-bold list-none">
|
||||||
{links.map((item, index) => (
|
{links.map((item, index) => (
|
||||||
<li>
|
<li class="decoration-transparent">
|
||||||
<a href={item.link}>{item.label}</a>
|
<a href={item.link}>{item.label}</a>
|
||||||
</li>
|
</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;
|
title: string;
|
||||||
}
|
}
|
||||||
const { title } = Astro.props;
|
const { title } = Astro.props;
|
||||||
|
import "@/global.css"
|
||||||
---
|
---
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@ -30,7 +31,7 @@ const { title } = Astro.props;
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</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 />
|
<slot />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -8,10 +8,8 @@ const { title } = Astro.props;
|
|||||||
|
|
||||||
<BaseLayout title={title}>
|
<BaseLayout title={title}>
|
||||||
<Header/>
|
<Header/>
|
||||||
<div class="min-h-screen flex flex-col">
|
<main class="flex-grow">
|
||||||
<main class="flex-grow">
|
<slot/>
|
||||||
<slot/>
|
</main>
|
||||||
</main>
|
<Footer/>
|
||||||
<Footer/>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
</BaseLayout>
|
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 ContentLayout from "@/layouts/ContentLayout.astro";
|
||||||
import { getPosts } from "@/utils/payload";
|
import { getPosts } from "@/utils/payload";
|
||||||
|
|
||||||
@ -6,9 +7,9 @@ const posts = await getPosts();
|
|||||||
---
|
---
|
||||||
|
|
||||||
<ContentLayout title="Paystro">
|
<ContentLayout title="Paystro">
|
||||||
<main class="" >
|
<main class="flex flex-col gap-4" >
|
||||||
<h1 class="font-bold text-5xl">Paystro</h1>
|
<h1 class="">Paystro</h1>
|
||||||
<p class="mt-3 text-lg">
|
<p class="mt-3">
|
||||||
Paystro is a pre-configured setup for Astro and Payloadcms that makes it
|
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
|
easy to get started with building your website. With Paystro, you'll have
|
||||||
a complete development environment that you can run locally using Docker.
|
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
|
reverse proxy. This setup provides a secure and scalable production
|
||||||
environment for your website.
|
environment for your website.
|
||||||
</p>
|
</p>
|
||||||
<h2 class="mt-6 font-bold text-2xl">Posts</h2>
|
<section class="mt-4">
|
||||||
<div class="flex gap-4 mt-3 flex-wrap">
|
<Posts posts={posts}/>
|
||||||
{
|
</section>
|
||||||
posts.length > 0 ? (
|
</main>
|
||||||
posts.map((post) => (
|
</ContentLayout>
|
||||||
<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>
|
|
||||||
</main>
|
|
||||||
</CLayout>
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
---
|
---
|
||||||
import ContentLayout from "@/layouts/ContentLayout.astro";
|
import ContentLayout from "@/layouts/ContentLayout.astro";
|
||||||
import Content from "@/components/Content.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 { getPost, getPosts } from "@/utils/payload";
|
||||||
|
import Author from "@/components/Author.astro";
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const posts = await getPosts();
|
const posts = await getPosts();
|
||||||
@ -18,13 +19,16 @@ const post = id && (await getPost(id));
|
|||||||
|
|
||||||
{
|
{
|
||||||
post ? (
|
post ? (
|
||||||
<ContentLayout title={`Paystro | ${post.title!}`}>
|
<ContentLayout title={`${post.title!}`}>
|
||||||
<div class="space-y-3 my-3">
|
<article class="space-y-3 my-3 max-w-prose">
|
||||||
<a href="/">BACK</a>
|
<h1 class="">{post.title}</h1>
|
||||||
<h1 class="font-bold text-5xl">{post.title}</h1>
|
|
||||||
{post.content && <Content content={post.content} />}
|
{post.content && <Content content={post.content} />}
|
||||||
</div>
|
</article>
|
||||||
</CLayout>
|
{typeof post.author === 'object' &&
|
||||||
|
<aside class="mt-8">
|
||||||
|
<Author author={post.author} />
|
||||||
|
</aside>}
|
||||||
|
</ContentLayout>
|
||||||
) : (
|
) : (
|
||||||
<div>404</div>
|
<div>404</div>
|
||||||
)
|
)
|
||||||
|
@ -12,12 +12,13 @@ export const getContentArray = (content: any) => {
|
|||||||
src: node.value.filename,
|
src: node.value.filename,
|
||||||
width: `${node.value.width}`,
|
width: `${node.value.width}`,
|
||||||
height: `${node.value.height}`,
|
height: `${node.value.height}`,
|
||||||
|
alt: `${node.value.alt}`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}).replaceAll("<p></p>", "<p> </p>");
|
}).replaceAll("<p></p>", "<p> </p>");
|
||||||
const htmlImageArray: (
|
const htmlImageArray: (
|
||||||
| string
|
| string
|
||||||
| { src: string; width: number; height: number }
|
| { src: string; width: number; height: number, alt: string }
|
||||||
)[] = [];
|
)[] = [];
|
||||||
let lastIndex = 0;
|
let lastIndex = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -33,6 +34,7 @@ export const getContentArray = (content: any) => {
|
|||||||
src: imgTag.match(/src="(.*?)"/)![1],
|
src: imgTag.match(/src="(.*?)"/)![1],
|
||||||
width: +imgTag.match(/width="(.*?)"/)![1],
|
width: +imgTag.match(/width="(.*?)"/)![1],
|
||||||
height: +imgTag.match(/height="(.*?)"/)![1],
|
height: +imgTag.match(/height="(.*?)"/)![1],
|
||||||
|
alt: imgTag.match(/alt="(.*?)"/)![1],
|
||||||
};
|
};
|
||||||
htmlImageArray.push(remainingHtml, imgObject);
|
htmlImageArray.push(remainingHtml, imgObject);
|
||||||
lastIndex = imgEndIndex;
|
lastIndex = imgEndIndex;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { Post } from "@/types";
|
import type { Post } from "@/types/payload-types";
|
||||||
|
|
||||||
const url = import.meta.env.DEV
|
const url = import.meta.env.DEV
|
||||||
? "http://payload:3001"
|
? "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: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
gray: {
|
primary: "rgba(var(--primary))",
|
||||||
DEFAULT: "#111111",
|
secondary: "rgba(var(--secondary))",
|
||||||
light: "#888888",
|
tertiary: "rgba(var(--tertiary))",
|
||||||
dark: "#222222",
|
text: "rgba(var(--text))",
|
||||||
},
|
textInverted: "rgba(var(--textInverted))",
|
||||||
|
link: "rgba(var(--link))",
|
||||||
|
background: "rgba(var(--background))",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
"types": ["@astrojs/image/client"],
|
"types": ["@astrojs/image/client"],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"],
|
||||||
|
"@/types/*": ["../*"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ services:
|
|||||||
- "MONGODB_PASSWORD_FILE=/run/secrets/mongo_password"
|
- "MONGODB_PASSWORD_FILE=/run/secrets/mongo_password"
|
||||||
- "TOKEN_FILE=/run/secrets/token"
|
- "TOKEN_FILE=/run/secrets/token"
|
||||||
- "REPOSITORY"
|
- "REPOSITORY"
|
||||||
|
- "DRONE_URL"
|
||||||
secrets:
|
secrets:
|
||||||
- mongo_password
|
- mongo_password
|
||||||
- payload_secret
|
- payload_secret
|
||||||
|
@ -19,11 +19,12 @@ services:
|
|||||||
- payload
|
- payload
|
||||||
|
|
||||||
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
|
container_name: ${NAME}-payload
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
build:
|
build:
|
||||||
context: payload
|
context: payload
|
||||||
|
target: dev
|
||||||
environment:
|
environment:
|
||||||
DEV: 1
|
DEV: 1
|
||||||
NAME: ${NAME}
|
NAME: ${NAME}
|
||||||
@ -31,15 +32,11 @@ services:
|
|||||||
PAYLOAD_URL: "http://localhost:${PAYLOAD_PORT}"
|
PAYLOAD_URL: "http://localhost:${PAYLOAD_PORT}"
|
||||||
PAYLOAD_PORT: ${PAYLOAD_PORT}
|
PAYLOAD_PORT: ${PAYLOAD_PORT}
|
||||||
PAYLOAD_SECRET: ${PAYLOAD_SECRET}
|
PAYLOAD_SECRET: ${PAYLOAD_SECRET}
|
||||||
MONGODB_USER:
|
MONGODB_URI: "mongodb://$MONGODB_USER:$MONGODB_PASSWORD@mongo:27017"
|
||||||
MONGODB_HOST: mongo
|
|
||||||
MONGODB_PASSWORD:
|
|
||||||
MONGODB_PORT: 27017
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./payload/src:/base/src
|
- ./payload/src:/base/src
|
||||||
- ./astro/src/types.ts:/types.ts
|
# - ./astro/src/types.ts:/types.ts
|
||||||
volumes:
|
- payload_media:/base/src/media
|
||||||
- payload_media:/prod/dist/media
|
|
||||||
ports:
|
ports:
|
||||||
- ${PAYLOAD_PORT}:${PAYLOAD_PORT}
|
- ${PAYLOAD_PORT}:${PAYLOAD_PORT}
|
||||||
networks:
|
networks:
|
||||||
|
@ -4,7 +4,10 @@
|
|||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"version": "1.2",
|
"version": "1.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "docker compose up",
|
"dev": "yarn dev:docker & yarn payload:types",
|
||||||
"stop": "docker compose down"
|
"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
|
EXPOSE 3001
|
||||||
CMD ["yarn","dev"]
|
CMD ["yarn","dev"]
|
||||||
|
|
||||||
|
|
||||||
FROM base AS build
|
FROM base AS build
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
@ -6,17 +6,22 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
|
"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: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",
|
"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": {
|
"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",
|
"cross-env": "^7.0.3",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"payload": "^1.15.6",
|
"payload": "^2.18.3",
|
||||||
"tsconfig-paths": "^4.2.0"
|
"tsconfig-paths": "^4.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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";
|
import { CollectionConfig } from "payload/types";
|
||||||
|
|
||||||
export const Media: CollectionConfig = {
|
export const Media: CollectionConfig = {
|
||||||
@ -5,19 +6,20 @@ export const Media: CollectionConfig = {
|
|||||||
admin: {},
|
admin: {},
|
||||||
access: {
|
access: {
|
||||||
read: (): boolean => true,
|
read: (): boolean => true,
|
||||||
create: () => true,
|
create: isUser,
|
||||||
update: () => true,
|
update: isUser,
|
||||||
|
delete: isUser,
|
||||||
},
|
},
|
||||||
upload: {
|
upload: {
|
||||||
staticURL: "/media",
|
staticURL: "/media",
|
||||||
staticDir: "media",
|
staticDir: "media",
|
||||||
mimeTypes: ["image/*"],
|
mimeTypes: ["image/*"],
|
||||||
},
|
},
|
||||||
|
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: "alt",
|
name: "alt",
|
||||||
type: "text",
|
type: "text",
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
import { CollectionConfig } from "payload/types";
|
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 = {
|
const Posts: CollectionConfig = {
|
||||||
slug: "posts",
|
slug: "posts",
|
||||||
|
versions: true,
|
||||||
admin: {
|
admin: {
|
||||||
defaultColumns: ["title", "author", "status"],
|
defaultColumns: ["title", "author", "status"],
|
||||||
useAsTitle: "title",
|
useAsTitle: "title",
|
||||||
},
|
},
|
||||||
access: {
|
access: {
|
||||||
|
//TODO: Author can CRUD own post
|
||||||
|
create: isUser,
|
||||||
read: () => true,
|
read: () => true,
|
||||||
create: () => true,
|
update: isEditor,
|
||||||
update: () => true,
|
delete: isEditor,
|
||||||
},
|
},
|
||||||
hooks: {
|
hooks: {
|
||||||
afterChange: [
|
afterChange: [
|
||||||
@ -19,7 +28,7 @@ const Posts: CollectionConfig = {
|
|||||||
process.env.NODE_ENV !== "development" &&
|
process.env.NODE_ENV !== "development" &&
|
||||||
console.log(
|
console.log(
|
||||||
await fetch(
|
await fetch(
|
||||||
`https://drone.autonomic.zone/api/repos/${process.env.REPOSITORY}/builds`,
|
`${process.env.DRONE_URL}/api/repos/${process.env.REPOSITORY}/builds`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@ -38,41 +47,64 @@ const Posts: CollectionConfig = {
|
|||||||
{
|
{
|
||||||
name: "title",
|
name: "title",
|
||||||
type: "text",
|
type: "text",
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "hallo",
|
name: "summary",
|
||||||
type: "text",
|
type: "text",
|
||||||
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "publishedDate",
|
name: "publishedDate",
|
||||||
type: "date",
|
type: "date",
|
||||||
},
|
required: false,
|
||||||
|
|
||||||
{
|
|
||||||
name: "content",
|
|
||||||
type: "richText",
|
|
||||||
admin: {
|
admin: {
|
||||||
elements: ["h2", "h3", "h4", "link", "ol", "ul", "upload"],
|
position: "sidebar",
|
||||||
leaves: ["bold", "italic", "underline"],
|
}
|
||||||
upload: {
|
},
|
||||||
collections: {
|
{
|
||||||
media: {
|
name: "thumbnail",
|
||||||
fields: [
|
type: "upload",
|
||||||
{
|
relationTo: "media",
|
||||||
name: "imagel",
|
required: true,
|
||||||
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: 'authors',
|
||||||
|
admin: {
|
||||||
|
position: "sidebar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "status",
|
name: "status",
|
||||||
type: "select",
|
type: "select",
|
||||||
|
required: true,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: "draft",
|
value: "draft",
|
||||||
@ -82,6 +114,10 @@ const Posts: CollectionConfig = {
|
|||||||
value: "published",
|
value: "published",
|
||||||
label: "Published",
|
label: "Published",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "archived",
|
||||||
|
label: "Archived",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
defaultValue: "draft",
|
defaultValue: "draft",
|
||||||
admin: {
|
admin: {
|
||||||
|
@ -1,20 +1,41 @@
|
|||||||
import { CollectionConfig } from 'payload/types';
|
import { CollectionConfig } from 'payload/types';
|
||||||
|
import { isAdmin, isAdminFieldLevel } from '../access/isAdmin';
|
||||||
|
import { isAdminOrSelf } from '../access/isAdminOrSelf';
|
||||||
|
|
||||||
const Users: CollectionConfig = {
|
const Users: CollectionConfig = {
|
||||||
slug: 'users',
|
slug: 'users',
|
||||||
auth: true,
|
auth: true,
|
||||||
admin: {
|
admin: {
|
||||||
|
defaultColumns: ["roles", "email"],
|
||||||
useAsTitle: 'email',
|
useAsTitle: 'email',
|
||||||
},
|
},
|
||||||
access: {
|
access: {
|
||||||
read: () => true,
|
create: isAdmin,
|
||||||
|
read: isAdminOrSelf,
|
||||||
|
update: isAdminOrSelf,
|
||||||
|
delete: isAdmin,
|
||||||
},
|
},
|
||||||
fields: [
|
fields: [
|
||||||
// Email added by default
|
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'roles',
|
||||||
type: 'text',
|
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 path from "path";
|
||||||
import Posts from "@/collections/Posts";
|
import Posts from "@/collections/Posts";
|
||||||
import Users from "@/collections/Users";
|
import Users from "@/collections/Users";
|
||||||
|
import Authors from "./collections/Authors";
|
||||||
import Media from "@/collections/Media";
|
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({
|
export default buildConfig({
|
||||||
serverURL: process.env.PAYLOAD_URL,
|
serverURL: process.env.PAYLOAD_URL,
|
||||||
admin: {
|
admin: {
|
||||||
user: Users.slug,
|
user: Users.slug,
|
||||||
|
bundler: webpackBundler(),
|
||||||
webpack: (config) => ({
|
webpack: (config) => ({
|
||||||
...config,
|
...config,
|
||||||
resolve: {
|
resolve: {
|
||||||
@ -19,8 +26,13 @@ export default buildConfig({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
collections: [Posts, Users, Media],
|
collections: [Posts, Users, Authors, Media],
|
||||||
typescript: {
|
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({
|
payload.init({
|
||||||
secret: process.env.PAYLOAD_SECRET,
|
secret: process.env.PAYLOAD_SECRET,
|
||||||
mongoURL: process.env.MONGODB_URI,
|
//mongoURL: process.env.MONGODB_URI,
|
||||||
express: app,
|
express: app,
|
||||||
onInit: () => {
|
onInit: () => {
|
||||||
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
|
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*", "./dist/*", "./dist/src/*"]
|
"@/*": ["./src/*", "./dist/*", "./dist/src/*"],
|
||||||
|
"@/types/*": ["../*"]
|
||||||
},
|
},
|
||||||
"jsx": "react"
|
"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