Compare commits
10 Commits
924a121eef
...
9628eea874
| Author | SHA1 | Date | |
|---|---|---|---|
| 9628eea874 | |||
| 459873f0e5 | |||
| 9b078254ac | |||
| d74105529e | |||
| c4caaddb10 | |||
| f3b71ecb32 | |||
| 1cb1c93739 | |||
| 82a56b8221 | |||
| 7455c30510 | |||
| b463a8f5b8 |
100
.gitignore
vendored
100
.gitignore
vendored
@@ -1 +1,99 @@
|
||||
node_modules
|
||||
# Strapi
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
.DS_Store
|
||||
.idea
|
||||
|
||||
### Astro ###
|
||||
# build output
|
||||
dist/
|
||||
|
||||
# Local fonts
|
||||
fonts/
|
||||
|
||||
# cache files
|
||||
.eslintcache
|
||||
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
||||
# Local Vercel folder
|
||||
.vercel
|
||||
262
DOCKER_README.md
Normal file
262
DOCKER_README.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# Strapi with PostgreSQL and Astro Client - Docker Compose Setup
|
||||
|
||||
This setup provides a complete Docker Compose environment for running Strapi with PostgreSQL and an Astro frontend client.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker
|
||||
- Docker Compose
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Clone and navigate to the project directory**
|
||||
```bash
|
||||
cd /path/to/your/project
|
||||
```
|
||||
|
||||
2. **Install PostgreSQL dependencies**
|
||||
```bash
|
||||
cd server
|
||||
npm install pg
|
||||
cd ..
|
||||
```
|
||||
|
||||
3. **Generate secure environment variables**
|
||||
```bash
|
||||
# Generate JWT secrets (you can use any secure random string generator)
|
||||
echo "JWT_SECRET=$(openssl rand -base64 32)"
|
||||
echo "ADMIN_JWT_SECRET=$(openssl rand -base64 32)"
|
||||
echo "APP_KEYS=$(openssl rand -base64 32),$(openssl rand -base64 32),$(openssl rand -base64 32),$(openssl rand -base64 32)"
|
||||
echo "API_TOKEN_SALT=$(openssl rand -base64 32)"
|
||||
echo "TRANSFER_TOKEN_SALT=$(openssl rand -base64 32)"
|
||||
```
|
||||
|
||||
4. **Create a .env file** (copy from docker-compose.env and update with your generated secrets)
|
||||
```bash
|
||||
cp docker-compose.env .env
|
||||
# Edit .env file with your generated secrets
|
||||
```
|
||||
|
||||
5. **Start the services**
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
6. **Access the applications**
|
||||
- Strapi Admin Panel: http://localhost:1337/admin
|
||||
- Strapi API: http://localhost:1337/api
|
||||
- Astro Client: http://localhost:4321
|
||||
|
||||
## Environment Variables Setup
|
||||
|
||||
### Automatic Setup (Recommended)
|
||||
Use the provided setup script to automatically configure environment variables:
|
||||
|
||||
```bash
|
||||
./setup-env.sh
|
||||
```
|
||||
|
||||
This script will:
|
||||
- Copy `docker-compose.env` to `.env`
|
||||
- Generate secure random secrets for JWT tokens and salts
|
||||
- Set up all necessary environment variables
|
||||
|
||||
### Manual Setup
|
||||
If you prefer to set up environment variables manually:
|
||||
|
||||
1. **Copy the environment template**
|
||||
```bash
|
||||
cp docker-compose.env .env
|
||||
```
|
||||
|
||||
2. **Edit the .env file**
|
||||
```bash
|
||||
nano .env # or use your preferred editor
|
||||
```
|
||||
|
||||
3. **Update sensitive values**
|
||||
- Replace all `your-*-here-change-this-in-production` values with secure random strings
|
||||
- Modify database credentials if needed
|
||||
- Adjust URLs and ports as required
|
||||
|
||||
### Environment Variables Reference
|
||||
|
||||
The following variables are available in `docker-compose.env`:
|
||||
|
||||
#### Database Configuration
|
||||
- `DATABASE_CLIENT`: Database type (postgres)
|
||||
- `DATABASE_HOST`: Database host (postgres)
|
||||
- `DATABASE_PORT`: Database port (5432)
|
||||
- `DATABASE_NAME`: Database name (strapi)
|
||||
- `DATABASE_USERNAME`: Database username (strapi)
|
||||
- `DATABASE_PASSWORD`: Database password (strapi_password)
|
||||
- `DATABASE_SSL`: SSL connection (false)
|
||||
|
||||
#### Strapi Configuration
|
||||
- `NODE_ENV`: Environment (development/production)
|
||||
- `JWT_SECRET`: JWT signing secret
|
||||
- `ADMIN_JWT_SECRET`: Admin JWT signing secret
|
||||
- `APP_KEYS`: Application keys (comma-separated)
|
||||
- `API_TOKEN_SALT`: API token salt
|
||||
- `TRANSFER_TOKEN_SALT`: Transfer token salt
|
||||
- `HOST`: Server host (0.0.0.0)
|
||||
- `PORT`: Server port (1337)
|
||||
|
||||
#### Client Configuration
|
||||
- `CLIENT_NODE_ENV`: Client environment (development/production)
|
||||
- `STRAPI_URL`: Strapi API URL (http://strapi:1337)
|
||||
|
||||
### Security Notes
|
||||
- The `.env` file is gitignored for security reasons
|
||||
- Never commit sensitive secrets to version control
|
||||
- Use different secrets for development and production
|
||||
- Regularly rotate secrets in production environments
|
||||
|
||||
## Services
|
||||
|
||||
### Strapi Application (Backend)
|
||||
- **Port**: 1337
|
||||
- **Container**: strapi-app
|
||||
- **Database**: PostgreSQL
|
||||
- **Purpose**: Headless CMS and API
|
||||
|
||||
### Astro Client (Frontend)
|
||||
- **Port**: 4321
|
||||
- **Container**: astro-client
|
||||
- **Purpose**: Frontend application
|
||||
- **Backend**: Connects to Strapi API
|
||||
|
||||
### PostgreSQL Database
|
||||
- **Port**: 5432
|
||||
- **Container**: strapi-postgres
|
||||
- **Database**: strapi
|
||||
- **Username**: strapi
|
||||
- **Password**: strapi_password
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The following environment variables are configured:
|
||||
|
||||
### Strapi Configuration
|
||||
- `DATABASE_CLIENT`: postgres
|
||||
- `DATABASE_HOST`: postgres (Docker service name)
|
||||
- `DATABASE_PORT`: 5432
|
||||
- `DATABASE_NAME`: strapi
|
||||
- `DATABASE_USERNAME`: strapi
|
||||
- `DATABASE_PASSWORD`: strapi_password
|
||||
- `NODE_ENV`: development
|
||||
|
||||
### Client Configuration
|
||||
- `NODE_ENV`: development
|
||||
- `STRAPI_URL`: http://strapi:1337 (internal Docker network)
|
||||
|
||||
## Volumes
|
||||
|
||||
- `postgres_data`: Persistent PostgreSQL data
|
||||
- `./server/public/uploads`: Strapi uploads directory
|
||||
- `./server/.tmp`: Strapi temporary files
|
||||
- `./client/src`: Astro source code (for development)
|
||||
- `./client/public`: Astro public assets
|
||||
- `./client/astro.config.mjs`: Astro configuration
|
||||
- `./client/tsconfig.json`: TypeScript configuration
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# Start all services
|
||||
docker-compose up -d
|
||||
|
||||
# View logs for specific services
|
||||
docker-compose logs -f strapi
|
||||
docker-compose logs -f client
|
||||
docker-compose logs -f postgres
|
||||
|
||||
# View all logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Stop all services
|
||||
docker-compose down
|
||||
|
||||
# Stop and remove volumes (WARNING: This will delete all data)
|
||||
docker-compose down -v
|
||||
|
||||
# Rebuild and start
|
||||
docker-compose up -d --build
|
||||
|
||||
# Rebuild specific service
|
||||
docker-compose up -d --build client
|
||||
|
||||
# Access PostgreSQL directly
|
||||
docker-compose exec postgres psql -U strapi -d strapi
|
||||
|
||||
# Access Strapi container
|
||||
docker-compose exec strapi sh
|
||||
|
||||
# Access Astro client container
|
||||
docker-compose exec client sh
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Hot Reload
|
||||
Both Strapi and Astro support hot reloading in development mode:
|
||||
- Changes to Strapi files will automatically restart the server
|
||||
- Changes to Astro files will automatically reload the browser
|
||||
|
||||
### API Communication
|
||||
The Astro client can communicate with Strapi using:
|
||||
- Internal Docker network: `http://strapi:1337`
|
||||
- External access: `http://localhost:1337`
|
||||
|
||||
### Example API call from Astro
|
||||
```javascript
|
||||
// In your Astro component or page
|
||||
const response = await fetch('http://strapi:1337/api/your-content-type');
|
||||
const data = await response.json();
|
||||
```
|
||||
|
||||
## First Time Setup
|
||||
|
||||
1. After starting the services, visit http://localhost:1337/admin
|
||||
2. Create your first admin user
|
||||
3. Configure your content types and permissions
|
||||
4. Visit http://localhost:4321 to see your Astro frontend
|
||||
5. Configure your Astro app to fetch data from Strapi
|
||||
|
||||
## Production Considerations
|
||||
|
||||
For production deployment:
|
||||
|
||||
1. Change all default passwords and secrets
|
||||
2. Use proper SSL certificates
|
||||
3. Set `NODE_ENV=production` for both services
|
||||
4. Configure proper backup strategies for PostgreSQL
|
||||
5. Use environment-specific configuration files
|
||||
6. Consider using Docker secrets for sensitive data
|
||||
7. Build the Astro app for production: `npm run build`
|
||||
8. Use a production-ready web server for the Astro build
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Strapi won't start
|
||||
- Check if PostgreSQL is running: `docker-compose logs postgres`
|
||||
- Verify database connection settings
|
||||
- Check if all environment variables are set correctly
|
||||
|
||||
### Astro client won't start
|
||||
- Check if Strapi is running: `docker-compose logs strapi`
|
||||
- Verify the STRAPI_URL environment variable
|
||||
- Check for port conflicts on 4321
|
||||
|
||||
### Database connection issues
|
||||
- Ensure PostgreSQL container is healthy: `docker-compose ps`
|
||||
- Check database logs: `docker-compose logs postgres`
|
||||
- Verify network connectivity between containers
|
||||
|
||||
### Permission issues
|
||||
- Ensure proper file permissions on mounted volumes
|
||||
- Check if the Docker user has access to the project files
|
||||
|
||||
### Network connectivity between services
|
||||
- Verify all services are on the same network: `docker network ls`
|
||||
- Check if services can reach each other using container names
|
||||
16
client/.dockerignore
Normal file
16
client/.dockerignore
Normal file
@@ -0,0 +1,16 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
.env
|
||||
.nyc_output
|
||||
coverage
|
||||
.cache
|
||||
.parcel-cache
|
||||
.next
|
||||
.nuxt
|
||||
dist
|
||||
build
|
||||
.astro
|
||||
.vscode
|
||||
26
client/Dockerfile
Normal file
26
client/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
FROM node:lts AS base
|
||||
WORKDIR /app
|
||||
|
||||
# By copying only the package.json and package-lock.json here, we ensure that the following `-deps` steps are independent of the source code.
|
||||
# Therefore, the `-deps` steps will be skipped if only the source code changes.
|
||||
COPY package.json package-lock.json ./
|
||||
|
||||
FROM base AS prod-deps
|
||||
RUN npm install --omit=dev
|
||||
|
||||
FROM base AS build-deps
|
||||
RUN npm install
|
||||
|
||||
FROM build-deps AS build
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM base AS runtime
|
||||
COPY --from=prod-deps /app/node_modules ./node_modules
|
||||
COPY --from=build /app/dist ./dist
|
||||
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=4321
|
||||
|
||||
EXPOSE 4321
|
||||
CMD node ./dist/server/entry.mjs
|
||||
@@ -3,9 +3,16 @@ import { defineConfig } from 'astro/config';
|
||||
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
import node from '@astrojs/node';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
output: 'server',
|
||||
vite: {
|
||||
plugins: [tailwindcss()]
|
||||
}
|
||||
},
|
||||
|
||||
adapter: node({
|
||||
mode: 'standalone'
|
||||
})
|
||||
});
|
||||
27
client/docker-compose.yml
Normal file
27
client/docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
client:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: astro-client
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "4321:4321"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
NODE_ENV: ${NODE_ENV}
|
||||
STRAPI_URL: ${STRAPI_URL}
|
||||
volumes:
|
||||
- ./src:/opt/app/src
|
||||
- ./public:/opt/app/public
|
||||
- ./astro.config.mjs:/opt/app/astro.config.mjs
|
||||
- ./tsconfig.json:/opt/app/tsconfig.json
|
||||
networks:
|
||||
- strapi-network
|
||||
|
||||
networks:
|
||||
strapi-network:
|
||||
external: true
|
||||
188
client/package-lock.json
generated
188
client/package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "client",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^9.2.2",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"@types/qs": "^6.14.0",
|
||||
"astro": "^5.7.12",
|
||||
@@ -70,6 +71,20 @@
|
||||
"vfile": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/node": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/node/-/node-9.2.2.tgz",
|
||||
"integrity": "sha512-PtLPuuojmcl9O3CEvXqL/D+wB4x5DlbrGOvP0MeTAh/VfKFprYAzgw1+45xsnTO+QvPWb26l1cT+ZQvvohmvMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@astrojs/internal-helpers": "0.6.1",
|
||||
"send": "^1.2.0",
|
||||
"server-destroy": "^1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/prism": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.2.0.tgz",
|
||||
@@ -1433,6 +1448,15 @@
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
@@ -1532,12 +1556,27 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
|
||||
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
@@ -1639,6 +1678,12 @@
|
||||
"@esbuild/win32-x64": "0.25.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
@@ -1660,6 +1705,15 @@
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
@@ -1728,6 +1782,15 @@
|
||||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
||||
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
@@ -2060,6 +2123,31 @@
|
||||
"integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors/node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/import-meta-resolve": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
|
||||
@@ -2070,6 +2158,12 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/iron-webcrypto": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
|
||||
@@ -3274,6 +3368,27 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
||||
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "^1.54.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
@@ -3435,6 +3550,18 @@
|
||||
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/oniguruma-parser": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz",
|
||||
@@ -3645,6 +3772,15 @@
|
||||
"integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
@@ -3942,6 +4078,40 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
|
||||
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.5",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"etag": "^1.8.1",
|
||||
"fresh": "^2.0.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"mime-types": "^3.0.1",
|
||||
"ms": "^2.1.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"range-parser": "^1.2.1",
|
||||
"statuses": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/server-destroy": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz",
|
||||
"integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
|
||||
@@ -4117,6 +4287,15 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||
@@ -4223,6 +4402,15 @@
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^9.2.2",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"@types/qs": "^6.14.0",
|
||||
"astro": "^5.7.12",
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { defineCollection, z } from "astro:content";
|
||||
import qs from "qs";
|
||||
|
||||
// Define a custom content collection that loads data from Strapi
|
||||
const strapiPoleElementsLoader = defineCollection({
|
||||
// Async loader function that fetches data from Strapi API
|
||||
loader: async () => {
|
||||
// Get Strapi URL from environment variables or fallback to localhost
|
||||
const BASE_URL = import.meta.env.STRAPI_URL || "http://localhost:1337";
|
||||
const path = "/api/elements";
|
||||
const url = new URL(path, BASE_URL);
|
||||
|
||||
// Build query parameters using qs to populate cover image data
|
||||
url.search = qs.stringify({
|
||||
populate: {
|
||||
mainImage: {
|
||||
fields: ["url", "alternativeText"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Fetch articles from Strapi
|
||||
const poleElementsData = await fetch(url.href);
|
||||
const { data }= await poleElementsData.json();
|
||||
|
||||
// Transform the API response into the desired data structure
|
||||
return data.map((item) => ({
|
||||
id: item.id.toString(),
|
||||
name: item.name,
|
||||
title: item.name,
|
||||
description: item.description,
|
||||
createdAt: item.createdAt,
|
||||
updatedAt: item.updatedAt,
|
||||
publishedAt: item.publishedAt,
|
||||
mainImage: {
|
||||
id: Number(item.mainImage.id),
|
||||
documentId: item.mainImage.documentId,
|
||||
url: item.mainImage.url,
|
||||
alternativeText: item.mainImage.alternativeText,
|
||||
}
|
||||
}));
|
||||
},
|
||||
// Define the schema for type validation using Zod
|
||||
schema: z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
publishedAt: z.string(),
|
||||
mainImage: z.object({
|
||||
id: z.number(),
|
||||
documentId: z.string(),
|
||||
url: z.string(),
|
||||
alternativeText: z.string(),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export default strapiPoleElementsLoader;
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
// Import necessary components and utilities
|
||||
import MarkdownComponent from "./MardownContent.astro";
|
||||
import { getStrapiMedia } from "../utils/strapi";
|
||||
import { getStrapiMedia } from "../lib/strapi";
|
||||
import { getStrapiBaseUrl } from "../config/strapi";
|
||||
import type { HTMLAttributes } from "astro/types";
|
||||
|
||||
@@ -18,19 +18,20 @@ const BASE_URL = getStrapiBaseUrl();
|
||||
<div {...otherProps}>
|
||||
{
|
||||
elements.map((poleElement) => (
|
||||
console.log(poleElement),
|
||||
<a href={`/elements/${poleElement.id}`} class="block">
|
||||
<article class="flex items-center bg-white rounded-lg shadow-lg overflow-hidden hover:shadow-xl transition-shadow duration-200">
|
||||
<img
|
||||
src={getStrapiMedia(
|
||||
poleElement.data.mainImage.url,
|
||||
poleElement.mainImage.url,
|
||||
BASE_URL,
|
||||
)}
|
||||
alt={poleElement.data.mainImage.alternativeText}
|
||||
alt={poleElement.mainImage.alternativeText}
|
||||
class="w-24 h-24 object-cover flex-shrink-0"
|
||||
/>
|
||||
<div class="p-4">
|
||||
<h2 class="text-xl font-bold">
|
||||
{poleElement.data.name}
|
||||
{poleElement.name}
|
||||
</h2>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import strapiPoleElementsLoader from "./collections/strapiPoleElementsLoader.mjs";
|
||||
|
||||
// Export the collection for use in Astro pages
|
||||
export const collections = {
|
||||
poleElements: strapiPoleElementsLoader,
|
||||
};
|
||||
3
client/src/env.d.ts
vendored
Normal file
3
client/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
interface ImportMetaEnv {
|
||||
readonly STRAPI_URL: string;
|
||||
}
|
||||
15
client/src/interfaces/poleElement.ts
Normal file
15
client/src/interfaces/poleElement.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export default interface PoleElement {
|
||||
id: string;
|
||||
name: string;
|
||||
title: string;
|
||||
description: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
publishedAt: string;
|
||||
mainImage: {
|
||||
id: number;
|
||||
documentId: string;
|
||||
url: string;
|
||||
alternativeText: string;
|
||||
};
|
||||
}
|
||||
68
client/src/lib/strapi.ts
Normal file
68
client/src/lib/strapi.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
interface Props {
|
||||
endpoint: string;
|
||||
query?: Record<string, string>;
|
||||
wrappedByKey?: string;
|
||||
wrappedByList?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches data from the Strapi API
|
||||
* @param endpoint - The endpoint to fetch from
|
||||
* @param query - The query parameters to add to the url
|
||||
* @param wrappedByKey - The key to unwrap the response from
|
||||
* @param wrappedByList - If the response is a list, unwrap it
|
||||
* @returns
|
||||
*/
|
||||
export default async function fetchApi<T>({
|
||||
endpoint,
|
||||
query,
|
||||
wrappedByKey,
|
||||
wrappedByList,
|
||||
}: Props): Promise<T> {
|
||||
if (endpoint.startsWith('/')) {
|
||||
endpoint = endpoint.slice(1);
|
||||
}
|
||||
const strapiUrl = import.meta.env.STRAPI_URL || process.env.STRAPI_URL || "http://localhost:1337";
|
||||
console.log(strapiUrl);
|
||||
const url = new URL(`${strapiUrl}/api/${endpoint}`);
|
||||
|
||||
if (query) {
|
||||
Object.entries(query).forEach(([key, value]) => {
|
||||
url.searchParams.append(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url.toString());
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP error! status: ${res.status}`);
|
||||
}
|
||||
|
||||
let data = await res.json();
|
||||
|
||||
if (wrappedByKey) {
|
||||
data = data[wrappedByKey];
|
||||
}
|
||||
|
||||
if (wrappedByList) {
|
||||
data = data[0];
|
||||
}
|
||||
|
||||
return data as T;
|
||||
} catch (error) {
|
||||
console.error('Error fetching from Strapi API:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to handle media URLs from Strapi
|
||||
export function getStrapiMedia(url: string | null, baseUrl: string) {
|
||||
if (url == null) return null;
|
||||
// Return as-is if it's a data URL (base64)
|
||||
if (url.startsWith("data:")) return url;
|
||||
// Return as-is if it's an absolute URL
|
||||
if (url.startsWith("http") || url.startsWith("//")) return url;
|
||||
// Prepend baseUrl for relative URLs
|
||||
return `${baseUrl}${url}`;
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
---
|
||||
// Import necessary components and utilities
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import PoleElementsList from "../components/PoleElementsList.astro";
|
||||
import { getStrapiBaseUrl } from "../config/strapi";
|
||||
|
||||
// Fetch all posts from Strapi using Astro's content collection
|
||||
const strapiPoleElements = await getCollection("poleElements");
|
||||
// Get Strapi URL from global config
|
||||
const BASE_URL = getStrapiBaseUrl();
|
||||
import fetchApi from '../lib/strapi';
|
||||
import type PoleElement from "../interfaces/poleElement";
|
||||
|
||||
const strapiPoleElements = await fetchApi<PoleElement[]>({
|
||||
endpoint: 'elements?populate=*', // the content type to fetch
|
||||
wrappedByKey: 'data', // the key to unwrap the response
|
||||
});
|
||||
---
|
||||
|
||||
<Layout title="Pole Elements" description="Pole Elements">
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import MardownContent from '../../components/MardownContent.astro';
|
||||
import Layout from '../../layouts/Layout.astro';
|
||||
|
||||
// 1. Genera una nueva ruta para cada entrada de colección
|
||||
export async function getStaticPaths() {
|
||||
const poleElements = await getCollection('poleElements');
|
||||
return poleElements.map(entry => ({
|
||||
params: { id: entry.id }, props: { entry },
|
||||
}));
|
||||
}
|
||||
// 2. Para tu plantilla, puedes obtener la entrada directamente de la prop
|
||||
const { entry } = Astro.props;
|
||||
---
|
||||
import fetchApi from '../../lib/strapi';
|
||||
import type PoleElement from '../../interfaces/poleElement';
|
||||
|
||||
<Layout title={entry.data.title} description={entry.data.description}>
|
||||
<h1>{entry.data.title}</h1>
|
||||
<MardownContent content={entry.data.description} />
|
||||
const { id } = Astro.params;
|
||||
|
||||
let poleElement: PoleElement;
|
||||
|
||||
try {
|
||||
poleElement = await fetchApi<PoleElement>({
|
||||
endpoint: 'elements',
|
||||
wrappedByKey: 'data',
|
||||
wrappedByList: true,
|
||||
query: {
|
||||
'populate': '*',
|
||||
'filters[id][$eq]': id || '',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return Astro.redirect('/404');
|
||||
}
|
||||
|
||||
---
|
||||
<Layout title={poleElement.name} description={poleElement.description}>
|
||||
<h1>{poleElement.name}</h1>
|
||||
{poleElement.description && <MardownContent content={poleElement.description} />}
|
||||
</Layout>
|
||||
@@ -1,10 +0,0 @@
|
||||
// Helper function to handle media URLs from Strapi
|
||||
export function getStrapiMedia(url: string | null, baseUrl: string) {
|
||||
if (url == null) return null;
|
||||
// Return as-is if it's a data URL (base64)
|
||||
if (url.startsWith("data:")) return url;
|
||||
// Return as-is if it's an absolute URL
|
||||
if (url.startsWith("http") || url.startsWith("//")) return url;
|
||||
// Prepend baseUrl for relative URLs
|
||||
return `${baseUrl}${url}`;
|
||||
}
|
||||
36
client/src/views/ElementView.astro
Normal file
36
client/src/views/ElementView.astro
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
import MardownContent from '../components/MardownContent.astro';
|
||||
|
||||
interface Props {
|
||||
entry: {
|
||||
data: {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const { entry } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="element-view">
|
||||
<h1>{entry.data.title}</h1>
|
||||
<MardownContent content={entry.data.description} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.element-view {
|
||||
max-width: 56rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.element-view h1 {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
</style>
|
||||
30
docker-compose.env
Normal file
30
docker-compose.env
Normal file
@@ -0,0 +1,30 @@
|
||||
# ========================================
|
||||
# Environment Variables for Docker Compose
|
||||
# ========================================
|
||||
# Copy this file to .env and modify values as needed
|
||||
# cp docker-compose.env .env
|
||||
|
||||
# Database Configuration
|
||||
DATABASE_CLIENT=postgres
|
||||
DATABASE_HOST=postgres
|
||||
DATABASE_PORT=5432
|
||||
DATABASE_NAME=strapi
|
||||
DATABASE_USERNAME=strapi
|
||||
DATABASE_PASSWORD=strapi_password
|
||||
DATABASE_SSL=false
|
||||
|
||||
# Strapi Configuration
|
||||
NODE_ENV=development
|
||||
JWT_SECRET=your-jwt-secret-here-change-this-in-production
|
||||
ADMIN_JWT_SECRET=your-admin-jwt-secret-here-change-this-in-production
|
||||
APP_KEYS=your-app-keys-here-change-this-in-production
|
||||
API_TOKEN_SALT=your-api-token-salt-here-change-this-in-production
|
||||
TRANSFER_TOKEN_SALT=your-transfer-token-salt-here-change-this-in-production
|
||||
|
||||
# Server Configuration
|
||||
HOST=0.0.0.0
|
||||
PORT=1337
|
||||
|
||||
# Client Configuration
|
||||
CLIENT_NODE_ENV=development
|
||||
STRAPI_URL=http://localhost:1337
|
||||
69
docker-compose.yml
Normal file
69
docker-compose.yml
Normal file
@@ -0,0 +1,69 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
strapi:
|
||||
build:
|
||||
context: ./server
|
||||
dockerfile: Dockerfile
|
||||
container_name: strapi-app
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "1337:1337"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
DATABASE_CLIENT: ${DATABASE_CLIENT}
|
||||
DATABASE_HOST: ${DATABASE_HOST}
|
||||
DATABASE_PORT: ${DATABASE_PORT}
|
||||
DATABASE_NAME: ${DATABASE_NAME}
|
||||
DATABASE_USERNAME: ${DATABASE_USERNAME}
|
||||
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
|
||||
DATABASE_SSL: ${DATABASE_SSL}
|
||||
NODE_ENV: ${NODE_ENV}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
|
||||
APP_KEYS: ${APP_KEYS}
|
||||
API_TOKEN_SALT: ${API_TOKEN_SALT}
|
||||
TRANSFER_TOKEN_SALT: ${TRANSFER_TOKEN_SALT}
|
||||
volumes:
|
||||
- ./server/public/uploads:/opt/app/public/uploads
|
||||
- ./server/.tmp:/opt/app/.tmp
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "nc", "-z", "localhost", "1337"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- strapi-network
|
||||
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: strapi-postgres
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- docker-compose.env
|
||||
environment:
|
||||
POSTGRES_DB: ${DATABASE_NAME}
|
||||
POSTGRES_USER: ${DATABASE_USERNAME}
|
||||
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DATABASE_USERNAME} -d ${DATABASE_NAME}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
networks:
|
||||
- strapi-network
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
|
||||
networks:
|
||||
strapi-network:
|
||||
driver: bridge
|
||||
17
server/.dockerignore
Normal file
17
server/.dockerignore
Normal file
@@ -0,0 +1,17 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
.env
|
||||
.nyc_output
|
||||
coverage
|
||||
.cache
|
||||
.parcel-cache
|
||||
.next
|
||||
.nuxt
|
||||
dist
|
||||
build
|
||||
.strapi-updater.json
|
||||
.strapi
|
||||
.tmp
|
||||
22
server/.env
22
server/.env
@@ -1,22 +0,0 @@
|
||||
|
||||
# Server
|
||||
HOST=0.0.0.0
|
||||
PORT=1337
|
||||
|
||||
# Secrets
|
||||
APP_KEYS=58Ak6T00Yd4tatybLqdT+g==,D5rtyrkpvP39ob0uGw2t/w==,uSd7HTABiHzhaZMNQ1X9og==,un/3ZhvRpwaPeRafk6MzFg==
|
||||
API_TOKEN_SALT=eyq17CXyGeimONpQa5GwLQ==
|
||||
ADMIN_JWT_SECRET=E+YDtxuMstheljK35LT0sg==
|
||||
TRANSFER_TOKEN_SALT=3wbGxS+rs1gFe9KxfTWwWA==
|
||||
ENCRYPTION_KEY=Nl+XKUJn3MIwS71P+KNFfg==
|
||||
|
||||
# Database
|
||||
DATABASE_CLIENT=sqlite
|
||||
DATABASE_HOST=
|
||||
DATABASE_PORT=
|
||||
DATABASE_NAME=
|
||||
DATABASE_USERNAME=
|
||||
DATABASE_PASSWORD=
|
||||
DATABASE_SSL=false
|
||||
DATABASE_FILENAME=.tmp/data.db
|
||||
JWT_SECRET=2jSJ125b/iVC4jMPSjSYKw==
|
||||
2
server/.gitignore
vendored
2
server/.gitignore
vendored
@@ -1 +1,3 @@
|
||||
node_modules
|
||||
public/uploads
|
||||
.tmp/
|
||||
Binary file not shown.
28
server/Dockerfile
Normal file
28
server/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
||||
FROM node:22-alpine
|
||||
|
||||
# Install netcat for port checking
|
||||
RUN apk add --no-cache netcat-openbsd
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /opt/app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci --only=production
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN npm run build
|
||||
|
||||
# Make startup script executable
|
||||
RUN chmod +x start.sh
|
||||
|
||||
# Expose port
|
||||
EXPOSE 1337
|
||||
|
||||
# Start the application with the startup script
|
||||
CMD ["./start.sh"]
|
||||
138
server/package-lock.json
generated
138
server/package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"@strapi/plugin-users-permissions": "^5.16.0",
|
||||
"@strapi/strapi": "^5.16.0",
|
||||
"better-sqlite3": "11.3.0",
|
||||
"pg": "^8.16.2",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-router-dom": "^6.0.0",
|
||||
@@ -13894,12 +13895,101 @@
|
||||
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
||||
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
||||
},
|
||||
"node_modules/pg": {
|
||||
"version": "8.16.2",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.2.tgz",
|
||||
"integrity": "sha512-OtLWF0mKLmpxelOt9BqVq83QV6bTfsS0XLegIeAKqKjurRnRKie1Dc1iL89MugmSLhftxw6NNCyZhm1yQFLMEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.9.1",
|
||||
"pg-pool": "^3.10.1",
|
||||
"pg-protocol": "^1.10.2",
|
||||
"pg-types": "2.2.0",
|
||||
"pgpass": "1.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"pg-cloudflare": "^1.2.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"pg-native": ">=3.0.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"pg-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pg-cloudflare": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.6.tgz",
|
||||
"integrity": "sha512-uxmJAnmIgmYgnSFzgOf2cqGQBzwnRYcrEgXuFjJNEkpedEIPBSEzxY7ph4uA9k1mI+l/GR0HjPNS6FKNZe8SBQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/pg-connection-string": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz",
|
||||
"integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg-int8": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-pool": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
|
||||
"integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"pg": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-protocol": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.2.tgz",
|
||||
"integrity": "sha512-Ci7jy8PbaWxfsck2dwZdERcDG2A0MG8JoQILs+uZNjABFuBuItAZCWUNz8sXRDMoui24rJw7WlXqgpMdBSN/vQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg-types": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pg-int8": "1.0.1",
|
||||
"postgres-array": "~2.0.0",
|
||||
"postgres-bytea": "~1.0.0",
|
||||
"postgres-date": "~1.0.4",
|
||||
"postgres-interval": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pg/node_modules/pg-connection-string": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
|
||||
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pgpass": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"split2": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -14861,6 +14951,45 @@
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/postgres-array": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-bytea": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-date": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-interval": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||
@@ -16981,6 +17110,15 @@
|
||||
"integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
"upgrade": "npx @strapi/upgrade latest",
|
||||
"upgrade:dry": "npx @strapi/upgrade latest --dry",
|
||||
"cs": "config-sync"
|
||||
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/plugin-cloud": "5.13.0",
|
||||
"@strapi/plugin-users-permissions": "^5.16.0",
|
||||
"@strapi/strapi": "^5.16.0",
|
||||
"better-sqlite3": "11.3.0",
|
||||
"pg": "^8.16.2",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-router-dom": "^6.0.0",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 642 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 116 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 387 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 194 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
17
server/start.sh
Normal file
17
server/start.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Start Strapi in the background
|
||||
npm start &
|
||||
STRAPI_PID=$!
|
||||
|
||||
# Wait for Strapi to be ready (check if port 1337 is listening)
|
||||
echo "Waiting for Strapi to be ready..."
|
||||
while ! nc -z localhost 1337; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Strapi is ready! Running config-sync import..."
|
||||
npm run cs import
|
||||
|
||||
# Wait for the Strapi process
|
||||
wait $STRAPI_PID
|
||||
57
setup-env.sh
Executable file
57
setup-env.sh
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Setup Environment Variables Script
|
||||
echo "Setting up environment variables..."
|
||||
|
||||
# Copy the docker-compose.env to .env if it doesn't exist
|
||||
if [ ! -f .env ]; then
|
||||
echo "Creating .env file from docker-compose.env..."
|
||||
cp docker-compose.env .env
|
||||
echo "✅ .env file created successfully!"
|
||||
else
|
||||
echo "⚠️ .env file already exists. Skipping..."
|
||||
fi
|
||||
|
||||
# Generate secure secrets if they haven't been changed from defaults
|
||||
echo ""
|
||||
echo "Checking for default secrets..."
|
||||
|
||||
# Function to generate a random string (alphanumeric only)
|
||||
generate_secret() {
|
||||
openssl rand -hex 32
|
||||
}
|
||||
|
||||
# Simple approach: create a new .env file with replacements
|
||||
if grep -q "your-jwt-secret-here-change-this-in-production" .env; then
|
||||
echo "Generating new JWT_SECRET..."
|
||||
sed "s/your-jwt-secret-here-change-this-in-production/$(generate_secret)/g" .env > .env.tmp && mv .env.tmp .env
|
||||
fi
|
||||
|
||||
if grep -q "your-admin-jwt-secret-here-change-this-in-production" .env; then
|
||||
echo "Generating new ADMIN_JWT_SECRET..."
|
||||
sed "s/your-admin-jwt-secret-here-change-this-in-production/$(generate_secret)/g" .env > .env.tmp && mv .env.tmp .env
|
||||
fi
|
||||
|
||||
if grep -q "your-app-keys-here-change-this-in-production" .env; then
|
||||
echo "Generating new APP_KEYS..."
|
||||
sed "s/your-app-keys-here-change-this-in-production/$(generate_secret)/g" .env > .env.tmp && mv .env.tmp .env
|
||||
fi
|
||||
|
||||
if grep -q "your-api-token-salt-here-change-this-in-production" .env; then
|
||||
echo "Generating new API_TOKEN_SALT..."
|
||||
sed "s/your-api-token-salt-here-change-this-in-production/$(generate_secret)/g" .env > .env.tmp && mv .env.tmp .env
|
||||
fi
|
||||
|
||||
if grep -q "your-transfer-token-salt-here-change-this-in-production" .env; then
|
||||
echo "Generating new TRANSFER_TOKEN_SALT..."
|
||||
sed "s/your-transfer-token-salt-here-change-this-in-production/$(generate_secret)/g" .env > .env.tmp && mv .env.tmp .env
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Environment setup complete!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Review and modify the .env file if needed"
|
||||
echo "2. Run: docker-compose up -d"
|
||||
echo ""
|
||||
echo "Note: The .env file is gitignored for security reasons."
|
||||
Reference in New Issue
Block a user