Merge pull request #93 from Worklenz/development

Development
This commit is contained in:
Chamika J
2025-04-28 17:23:04 +05:30
committed by GitHub
15 changed files with 330 additions and 208 deletions

34
.env
View File

@@ -1,34 +0,0 @@
# Database configuration
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=worklenz_db
DB_HOST=db
DB_PORT=5432
DB_MAX_CLIENTS=50
# Server configuration
NODE_ENV=development
PORT=3000
SESSION_NAME=worklenz.sid
SESSION_SECRET=worklenz-session-secret
COOKIE_SECRET=worklenz-cookie-secret
# CORS
SOCKET_IO_CORS=http://localhost:5000
SERVER_CORS=*
# Storage configuration (MinIO)
STORAGE_PROVIDER=s3
AWS_REGION=us-east-1
AWS_BUCKET=worklenz-bucket
S3_ACCESS_KEY_ID=minioadmin
S3_SECRET_ACCESS_KEY=minioadmin
S3_URL=http://minio:9000
# Application URLs
HOSTNAME=localhost:5000
FRONTEND_URL=http://localhost:5000
# For local development, set these to the frontend service
LOGIN_FAILURE_REDIRECT=http://localhost:5000
LOGIN_SUCCESS_REDIRECT=http://localhost:5000/auth/authenticate

View File

@@ -1,34 +0,0 @@
# Database configuration
DB_USER=postgres
DB_PASSWORD=your_db_password
DB_NAME=worklenz_db
DB_HOST=localhost
DB_PORT=5432
DB_MAX_CLIENTS=50
# Server configuration
NODE_ENV=development
PORT=3000
SESSION_NAME=worklenz.sid
SESSION_SECRET=your_session_secret
COOKIE_SECRET=your_cookie_secret
# CORS
SOCKET_IO_CORS=http://localhost:5000
SERVER_CORS=*
# Storage configuration
STORAGE_PROVIDER=s3
AWS_REGION=your_aws_region
AWS_BUCKET=your_bucket_name
S3_ACCESS_KEY_ID=your_access_key_id
S3_SECRET_ACCESS_KEY=your_secret_access_key
S3_URL=your_s3_url
# Application URLs
HOSTNAME=localhost:5000
FRONTEND_URL=http://localhost:5000
# For local development
LOGIN_FAILURE_REDIRECT=http://localhost:5000
LOGIN_SUCCESS_REDIRECT=http://localhost:5000/auth/authenticate

View File

@@ -358,34 +358,88 @@ const s3Client = new S3Client({
### Environment Configuration
The `.env` file includes the necessary configuration for using MinIO:
The project uses the following environment file structure:
```
STORAGE_PROVIDER=s3
AWS_REGION=us-east-1
AWS_BUCKET=worklenz-bucket
S3_ACCESS_KEY_ID=minioadmin
S3_SECRET_ACCESS_KEY=minioadmin
S3_URL=http://minio:9000
- **Frontend**:
- `worklenz-frontend/.env.development` - Development environment variables
- `worklenz-frontend/.env.production` - Production build variables
- **Backend**:
- `worklenz-backend/.env` - Backend environment variables
### Setting Up Environment Files
The Docker environment script will create or overwrite all environment files:
```bash
# For HTTP/WS
./update-docker-env.sh your-hostname
# For HTTPS/WSS
./update-docker-env.sh your-hostname true
```
When the backend service starts, it will use these environment variables to connect to MinIO for file storage.
This script generates properly configured environment files for both development and production environments.
## Development
## Docker Deployment
For development, you can use the provided Docker setup which includes all necessary dependencies. The code will be running inside containers, but you can still edit files locally and see changes reflected in real-time.
### Local Development with Docker
## Production Deployment
1. Set up the environment files:
```bash
# For HTTP/WS
./update-docker-env.sh
# For HTTPS/WSS
./update-docker-env.sh localhost true
```
For production deployment:
2. Run the application using Docker Compose:
```bash
docker-compose up -d
```
1. Update the `.env` file with production settings
2. Build custom Docker images or use the provided ones
3. Deploy using Docker Compose or a container orchestration platform like Kubernetes
3. Access the application:
- Frontend: http://localhost:5000
- Backend API: http://localhost:3000 (or https://localhost:3000 with SSL)
For MinIO in production, consider:
- Setting up proper credentials (change default minioadmin/minioadmin)
- Configuring persistent storage
- Setting up proper networking and access controls
- Using multiple MinIO instances for high availability
### Remote Server Deployment
When deploying to a remote server:
1. Set up the environment files with your server's hostname:
```bash
# For HTTP/WS
./update-docker-env.sh your-server-hostname
# For HTTPS/WSS
./update-docker-env.sh your-server-hostname true
```
This ensures that the frontend correctly connects to the backend API.
2. Pull and run the latest Docker images:
```bash
docker-compose pull
docker-compose up -d
```
3. Access the application through your server's hostname:
- Frontend: http://your-server-hostname:5000
- Backend API: http://your-server-hostname:3000
### Environment Configuration
The Docker setup uses environment variables to configure the services:
- Frontend:
- `VITE_API_URL`: URL of the backend API (default: http://backend:3000 for container networking)
- `VITE_SOCKET_URL`: WebSocket URL for real-time communication (default: ws://backend:3000)
- Backend:
- Database connection parameters
- Storage configuration
- Other backend settings
For custom configuration, edit the `.env` file or the `update-docker-env.sh` script.

View File

@@ -1,12 +1,16 @@
services:
frontend:
image: docker.io/chamikajaycey/worklenz-frontend:latest
build:
context: ./worklenz-frontend
dockerfile: Dockerfile
container_name: worklenz_frontend
ports:
- "5000:5000"
depends_on:
backend:
condition: service_started
env_file:
- ./worklenz-frontend/.env.production
networks:
- worklenz
@@ -20,36 +24,8 @@ services:
condition: service_healthy
minio:
condition: service_started
environment:
- AWS_REGION=${AWS_REGION:-us-east-1}
- BACKEND_PUBLIC_DIR
- BACKEND_VIEWS_DIR
- COMMIT_BUILD_IMMEDIATELY
- COOKIE_SECRET
- DB_HOST=${DB_HOST:-db}
- DB_MAX_CLIENTS
- DB_NAME=${DB_NAME:-worklenz_db}
- DB_PASSWORD=${DB_PASSWORD:-password}
- DB_PORT=${DB_PORT:-5432}
- DB_USER=${DB_USER:-postgres}
- GOOGLE_CALLBACK_URL
- GOOGLE_CLIENT_ID
- GOOGLE_CLIENT_SECRET
- HOSTNAME
- LOGIN_FAILURE_REDIRECT
- NODE_ENV=${NODE_ENV:-development}
- PORT=${PORT:-3000}
- SESSION_NAME
- SESSION_SECRET
- SLACK_WEBHOOK
- SOCKET_IO_CORS
- SOURCE_EMAIL
- USE_PG_NATIVE
- STORAGE_PROVIDER=${STORAGE_PROVIDER:-s3}
- AWS_BUCKET=${BUCKET:-worklenz-bucket}
- AWS_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID:-minioadmin}
- AWS_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY:-minioadmin}
- S3_URL=${S3_URL:-http://minio:9000}
env_file:
- ./worklenz-backend/.env
networks:
- worklenz
@@ -113,7 +89,23 @@ services:
- worklenz
volumes:
- worklenz_postgres_data:/var/lib/postgresql/data
- ./worklenz-backend/database/:/docker-entrypoint-initdb.d
- type: bind
source: ./worklenz-backend/database
target: /docker-entrypoint-initdb.d
consistency: cached
command: >
bash -c '
if command -v apt-get >/dev/null 2>&1; then
apt-get update && apt-get install -y dos2unix
elif command -v apk >/dev/null 2>&1; then
apk add --no-cache dos2unix
fi &&
find /docker-entrypoint-initdb.d -type f -name "*.sh" -exec sh -c '\''
dos2unix "{}" 2>/dev/null || true
chmod +x "{}"
'\'' \; &&
exec docker-entrypoint.sh postgres
'
volumes:
worklenz_postgres_data:

133
update-docker-env.sh Executable file
View File

@@ -0,0 +1,133 @@
#!/bin/bash
# Script to set environment variables for Docker deployment
# Usage: ./update-docker-env.sh [hostname] [use_ssl]
# Default hostname if not provided
DEFAULT_HOSTNAME="localhost"
HOSTNAME=${1:-$DEFAULT_HOSTNAME}
# Check if SSL should be used
USE_SSL=${2:-false}
# Set protocol prefixes based on SSL flag
if [ "$USE_SSL" = "true" ]; then
HTTP_PREFIX="https://"
WS_PREFIX="wss://"
else
HTTP_PREFIX="http://"
WS_PREFIX="ws://"
fi
# Frontend URLs
FRONTEND_URL="${HTTP_PREFIX}${HOSTNAME}:5000"
MINIO_DASHBOARD_URL="${HTTP_PREFIX}${HOSTNAME}:9001"
# Create or overwrite frontend .env.development file
mkdir -p worklenz-frontend
cat > worklenz-frontend/.env.development << EOL
# API Connection
VITE_API_URL=http://localhost:3000
VITE_SOCKET_URL=ws://localhost:3000
# Application Environment
VITE_APP_TITLE=Worklenz
VITE_APP_ENV=development
# Mixpanel
VITE_MIXPANEL_TOKEN=
# Recaptcha
VITE_ENABLE_RECAPTCHA=false
VITE_RECAPTCHA_SITE_KEY=
# Session ID
VITE_WORKLENZ_SESSION_ID=worklenz-session-id
EOL
# Create frontend .env.production file
cat > worklenz-frontend/.env.production << EOL
# API Connection
VITE_API_URL=${HTTP_PREFIX}${HOSTNAME}:3000
VITE_SOCKET_URL=${WS_PREFIX}${HOSTNAME}:3000
# Application Environment
VITE_APP_TITLE=Worklenz
VITE_APP_ENV=production
# Mixpanel
VITE_MIXPANEL_TOKEN=
# Recaptcha
VITE_ENABLE_RECAPTCHA=false
VITE_RECAPTCHA_SITE_KEY=
# Session ID
VITE_WORKLENZ_SESSION_ID=worklenz-session-id
EOL
# Create backend environment file
mkdir -p worklenz-backend
cat > worklenz-backend/.env << EOL
# Server
NODE_ENV=production
PORT=3000
SESSION_NAME=worklenz.sid
SESSION_SECRET=change_me_in_production
COOKIE_SECRET=change_me_in_production
# CORS
SOCKET_IO_CORS=${FRONTEND_URL}
SERVER_CORS=${FRONTEND_URL}
# Database
DB_HOST=db
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=worklenz_db
DB_MAX_CLIENTS=50
USE_PG_NATIVE=true
# Storage Configuration
STORAGE_PROVIDER=s3
AWS_REGION=us-east-1
AWS_BUCKET=worklenz-bucket
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin
S3_URL=http://minio:9000
# Backend Directories
BACKEND_PUBLIC_DIR=./public
BACKEND_VIEWS_DIR=./views
# Host
HOSTNAME=${HOSTNAME}
FRONTEND_URL=${FRONTEND_URL}
# Email
SOURCE_EMAIL=no-reply@example.com
# Notifications
SLACK_WEBHOOK=
# Other Settings
COMMIT_BUILD_IMMEDIATELY=true
# JWT Secret
JWT_SECRET=change_me_in_production
EOL
echo "Environment configuration updated for ${HOSTNAME} with" $([ "$USE_SSL" = "true" ] && echo "HTTPS/WSS" || echo "HTTP/WS")
echo "Created/updated environment files:"
echo "- worklenz-frontend/.env.development (development)"
echo "- worklenz-frontend/.env.production (production build)"
echo "- worklenz-backend/.env"
echo
echo "To run with Docker Compose, use: docker-compose up -d"
echo
echo "Frontend URL: ${FRONTEND_URL}"
echo "API URL: ${HTTP_PREFIX}${HOSTNAME}:3000"
echo "Socket URL: ${WS_PREFIX}${HOSTNAME}:3000"
echo "MinIO Dashboard URL: ${MINIO_DASHBOARD_URL}"
echo "CORS is configured to allow requests from: ${FRONTEND_URL}"

View File

@@ -1,76 +0,0 @@
# Server
NODE_ENV=development
PORT=3000
SESSION_NAME=worklenz.sid
SESSION_SECRET="your_session_secret"
COOKIE_SECRET="your_cookie_secret"
# CORS
SOCKET_IO_CORS=http://localhost:5000
SERVER_CORS=*
# Database
DB_USER=postgres
DB_PASSWORD=your_db_password
DB_NAME=worklenz_db
DB_HOST=localhost
DB_PORT=5432
DB_MAX_CLIENTS=50
# Google Login
GOOGLE_CLIENT_ID="your_google_client_id"
GOOGLE_CLIENT_SECRET="your_google_client_secret"
GOOGLE_CALLBACK_URL="http://localhost:5000/secure/google/verify"
LOGIN_FAILURE_REDIRECT="http://localhost:5000/auth/authenticating"
LOGIN_SUCCESS_REDIRECT="http://localhost:5000/auth/authenticating"
# CLI
ANGULAR_DIST_DIR="path/to/frontend/dist"
ANGULAR_SRC_DIR="path/to/frontend"
BACKEND_PUBLIC_DIR="path/to/backend/public"
BACKEND_VIEWS_DIR="path/to/backend/views"
COMMIT_BUILD_IMMEDIATELY=false
# HOST
HOSTNAME=localhost:5000
# SLACK
SLACK_WEBHOOK=your_slack_webhook_url
USE_PG_NATIVE=false
# JWT SECRET
JWT_SECRET=your_jwt_secret
# FRONTEND_URL
FRONTEND_URL=http://localhost:5000
# STORAGE
STORAGE_PROVIDER=s3 # values s3 or azure
# AWS
AWS_REGION="your_aws_region"
AWS_ACCESS_KEY_ID="your_aws_access_key_id"
AWS_SECRET_ACCESS_KEY="your_aws_secret_access_key"
AWS_BUCKET="your_s3_bucket"
S3_URL="your_s3_url"
# Azure Storage
AZURE_STORAGE_ACCOUNT_NAME="your_storage_account_name"
AZURE_STORAGE_CONTAINER="your_storage_container"
AZURE_STORAGE_ACCOUNT_KEY="your_storage_account_key"
AZURE_STORAGE_URL="your_storage_url"
# DIRECTPAY
DP_STAGE=DEV
DP_URL=your_url
DP_MERCHANT_ID=your_merchant_id
DP_SECRET_KEY=your_secret_key
DP_API_KEY=your_api_key
CONTACT_US_EMAIL=support@example.com
GOOGLE_CAPTCHA_SECRET_KEY=your_captcha_secret_key
GOOGLE_CAPTCHA_PASS_SCORE=0.8
# Email Cronjobs
ENABLE_EMAIL_CRONJOBS=true

View File

@@ -55,21 +55,25 @@ const allowedOrigins = [
isProduction()
? [
`http://localhost:5000`,
`http://127.0.0.1:5000`
]
`http://127.0.0.1:5000`,
process.env.SERVER_CORS || "", // Add hostname from env
process.env.FRONTEND_URL || "" // Support FRONTEND_URL as well
].filter(Boolean) // Remove empty strings
: [
"http://localhost:3000",
"http://localhost:5173",
"http://127.0.0.1:5173",
"http://127.0.0.1:3000",
"http://127.0.0.1:5000",
`http://localhost:5000`
]
`http://localhost:5000`,
process.env.SERVER_CORS || "", // Add hostname from env
process.env.FRONTEND_URL || "" // Support FRONTEND_URL as well
].filter(Boolean) // Remove empty strings
].flat();
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
if (!isProduction() || !origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
console.log("Blocked origin:", origin, process.env.NODE_ENV);

View File

@@ -5,5 +5,8 @@ import safeControllerFunction from "../../shared/safe-controller-function";
const public_router = express.Router();
public_router.post("/new-subscriber", safeControllerFunction(ClientsController.addSubscriber));
public_router.get("/health", (req, res) => {
res.status(200).json({ status: "ok" });
});
export default public_router;

View File

@@ -7,6 +7,11 @@ COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# Create env-config.js dynamically during build
RUN echo "window.VITE_API_URL='${VITE_API_URL:-http://backend:3000}';" > ./public/env-config.js && \
echo "window.VITE_SOCKET_URL='${VITE_SOCKET_URL:-ws://backend:3000}';" >> ./public/env-config.js
RUN npm run build
FROM node:22-alpine AS production
@@ -16,5 +21,17 @@ WORKDIR /app
RUN npm install -g serve
COPY --from=build /app/build /app/build
COPY --from=build /app/public/env-config.js /app/build/env-config.js
# Create start.sh script
RUN echo '#!/bin/sh' > /app/start.sh && \
echo '# Update env-config.js with runtime environment variables' >> /app/start.sh && \
echo 'cat > /app/build/env-config.js << EOL' >> /app/start.sh && \
echo 'window.VITE_API_URL="${VITE_API_URL:-http://backend:3000}";' >> /app/start.sh && \
echo 'window.VITE_SOCKET_URL="${VITE_SOCKET_URL:-ws://backend:3000}";' >> /app/start.sh && \
echo 'EOL' >> /app/start.sh && \
echo 'exec serve -s build -l 5000' >> /app/start.sh && \
chmod +x /app/start.sh
EXPOSE 5000
CMD ["serve", "-s", "build", "-l", "5000"]
CMD ["/app/start.sh"]

View File

@@ -12,6 +12,8 @@
rel="stylesheet"
/>
<title>Worklenz</title>
<!-- Environment configuration -->
<script src="/env-config.js"></script>
</head>
<body>

View File

@@ -2,6 +2,7 @@ import axios, { AxiosError } from 'axios';
import alertService from '@/services/alerts/alertService';
import logger from '@/utils/errorLogger';
import config from '@/config/env';
export const getCsrfToken = (): string | null => {
const match = document.cookie.split('; ').find(cookie => cookie.startsWith('XSRF-TOKEN='));
@@ -16,7 +17,7 @@ export const getCsrfToken = (): string | null => {
export const refreshCsrfToken = async (): Promise<string | null> => {
try {
// Make a GET request to the server to get a fresh CSRF token
await axios.get(`${import.meta.env.VITE_API_URL}/csrf-token`, { withCredentials: true });
await axios.get(`${config.apiUrl}/csrf-token`, { withCredentials: true });
return getCsrfToken();
} catch (error) {
console.error('Failed to refresh CSRF token:', error);
@@ -25,7 +26,7 @@ export const refreshCsrfToken = async (): Promise<string | null> => {
};
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_URL,
baseURL: config.apiUrl,
withCredentials: true,
headers: {
'Content-Type': 'application/json',

View File

@@ -6,13 +6,14 @@ import { IHomeTasksModel, IHomeTasksConfig } from '@/types/home/home-page.types'
import { IMyTask } from '@/types/home/my-tasks.types';
import { IProject } from '@/types/project/project.types';
import { getCsrfToken } from '../api-client';
import config from '@/config/env';
const rootUrl = '/home';
const api = createApi({
reducerPath: 'homePageApi',
baseQuery: fetchBaseQuery({
baseUrl: `${import.meta.env.VITE_API_URL}${API_BASE_URL}`,
baseUrl: `${config.apiUrl}${API_BASE_URL}`,
prepareHeaders: headers => {
headers.set('X-CSRF-Token', getCsrfToken() || '');
headers.set('Content-Type', 'application/json');

View File

@@ -6,13 +6,14 @@ import { IProjectsViewModel } from '@/types/project/projectsViewModel.types';
import { IServerResponse } from '@/types/common.types';
import { IProjectMembersViewModel } from '@/types/projectMember.types';
import { getCsrfToken } from '../api-client';
import config from '@/config/env';
const rootUrl = '/projects';
export const projectsApi = createApi({
reducerPath: 'projectsApi',
baseQuery: fetchBaseQuery({
baseUrl: `${import.meta.env.VITE_API_URL}${API_BASE_URL}`,
baseUrl: `${config.apiUrl}${API_BASE_URL}`,
prepareHeaders: headers => {
headers.set('X-CSRF-Token', getCsrfToken() || '');
headers.set('Content-Type', 'application/json');

View File

@@ -0,0 +1,56 @@
/**
* Environment configuration
* Reads from window environment variables (set by env-config.js)
* Falls back to import.meta.env variables (set during build time)
* Falls back to development defaults
*/
declare global {
interface Window {
VITE_API_URL?: string;
VITE_SOCKET_URL?: string;
}
}
export const getApiUrl = (): string => {
// First check runtime-injected environment variables
if (window.VITE_API_URL) {
return window.VITE_API_URL;
}
// Then check build-time environment variables
if (import.meta.env.VITE_API_URL) {
return import.meta.env.VITE_API_URL;
}
// Default for development
return 'http://localhost:3000';
};
export const getSocketUrl = (): string => {
// First check runtime-injected environment variables
if (window.VITE_SOCKET_URL) {
return window.VITE_SOCKET_URL;
}
// Then check build-time environment variables
if (import.meta.env.VITE_SOCKET_URL) {
return import.meta.env.VITE_SOCKET_URL;
}
// Default based on API URL (convert http->ws or https->wss)
const apiUrl = getApiUrl();
if (apiUrl.startsWith('https://')) {
return apiUrl.replace('https://', 'wss://');
} else if (apiUrl.startsWith('http://')) {
return apiUrl.replace('http://', 'ws://');
}
// Final fallback
return 'ws://localhost:3000';
};
export default {
apiUrl: getApiUrl(),
socketUrl: getSocketUrl(),
};

View File

@@ -1,5 +1,7 @@
import config from '@/config/env';
export const SOCKET_CONFIG = {
url: import.meta.env.VITE_SOCKET_URL || 'ws://localhost:3000',
url: config.socketUrl,
options: {
transports: ['websocket'],
path: '/socket',