Dockerized
Browse filesSigned-off-by: Vimal Kumar <[email protected]>
- README.md +21 -1
- crossword-app/.dockerignore +67 -0
- crossword-app/Dockerfile +36 -0
- crossword-app/README.md +49 -3
- crossword-app/backend/package.json +2 -0
- crossword-app/backend/src/app.js +50 -19
- crossword-app/frontend/src/hooks/useCrossword.js +1 -1
README.md
CHANGED
|
@@ -37,6 +37,8 @@ A full-stack web application for generating and solving crossword puzzles with v
|
|
| 37 |
|
| 38 |
## Running the Application
|
| 39 |
|
|
|
|
|
|
|
| 40 |
1. **Start the backend server**
|
| 41 |
```bash
|
| 42 |
cd crossword-app/backend
|
|
@@ -53,6 +55,19 @@ A full-stack web application for generating and solving crossword puzzles with v
|
|
| 53 |
|
| 54 |
3. **Open your browser** and navigate to `http://localhost:5173`
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
## How to Use
|
| 57 |
|
| 58 |
1. Select one or more topics from the available options
|
|
@@ -82,4 +97,9 @@ cross-words/
|
|
| 82 |
|
| 83 |
- **Frontend**: React, Vite, CSS
|
| 84 |
- **Backend**: Node.js, Express
|
| 85 |
-
- **Algorithm**: Backtracking-based crossword generation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
## Running the Application
|
| 39 |
|
| 40 |
+
### Development Mode
|
| 41 |
+
|
| 42 |
1. **Start the backend server**
|
| 43 |
```bash
|
| 44 |
cd crossword-app/backend
|
|
|
|
| 55 |
|
| 56 |
3. **Open your browser** and navigate to `http://localhost:5173`
|
| 57 |
|
| 58 |
+
### Docker Deployment
|
| 59 |
+
|
| 60 |
+
**Build and run with Docker:**
|
| 61 |
+
```bash
|
| 62 |
+
cd crossword-app
|
| 63 |
+
docker build -t crossword-app .
|
| 64 |
+
docker run -p 7860:7860 -e NODE_ENV=production crossword-app
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
Open `http://localhost:7860` to access the application.
|
| 68 |
+
|
| 69 |
+
**For Hugging Face Spaces:** Upload the `crossword-app/` directory to a new Docker Space.
|
| 70 |
+
|
| 71 |
## How to Use
|
| 72 |
|
| 73 |
1. Select one or more topics from the available options
|
|
|
|
| 97 |
|
| 98 |
- **Frontend**: React, Vite, CSS
|
| 99 |
- **Backend**: Node.js, Express
|
| 100 |
+
- **Algorithm**: Backtracking-based crossword generation
|
| 101 |
+
- **Deployment**: Docker (ready for Hugging Face Spaces)
|
| 102 |
+
|
| 103 |
+
## Documentation
|
| 104 |
+
|
| 105 |
+
For detailed technical documentation, deployment guides, and API specifications, see the [crossword-app README](./crossword-app/README.md).
|
crossword-app/.dockerignore
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Node modules
|
| 2 |
+
**/node_modules
|
| 3 |
+
**/npm-debug.log*
|
| 4 |
+
**/yarn-debug.log*
|
| 5 |
+
**/yarn-error.log*
|
| 6 |
+
|
| 7 |
+
# Build directories
|
| 8 |
+
frontend/dist
|
| 9 |
+
backend/public
|
| 10 |
+
|
| 11 |
+
# Development files
|
| 12 |
+
**/.git
|
| 13 |
+
**/.gitignore
|
| 14 |
+
**/.env.local
|
| 15 |
+
**/.env.development
|
| 16 |
+
**/.env.test
|
| 17 |
+
**/.vscode
|
| 18 |
+
**/.idea
|
| 19 |
+
|
| 20 |
+
# OS generated files
|
| 21 |
+
.DS_Store
|
| 22 |
+
.DS_Store?
|
| 23 |
+
._*
|
| 24 |
+
.Spotlight-V100
|
| 25 |
+
.Trashes
|
| 26 |
+
ehthumbs.db
|
| 27 |
+
Thumbs.db
|
| 28 |
+
|
| 29 |
+
# Logs
|
| 30 |
+
**/logs
|
| 31 |
+
**/*.log
|
| 32 |
+
|
| 33 |
+
# Runtime data
|
| 34 |
+
**/pids
|
| 35 |
+
**/*.pid
|
| 36 |
+
**/*.seed
|
| 37 |
+
**/*.pid.lock
|
| 38 |
+
|
| 39 |
+
# Coverage directory used by tools like istanbul
|
| 40 |
+
**/coverage
|
| 41 |
+
**/.nyc_output
|
| 42 |
+
|
| 43 |
+
# Optional npm cache directory
|
| 44 |
+
**/.npm
|
| 45 |
+
|
| 46 |
+
# Optional eslint cache
|
| 47 |
+
**/.eslintcache
|
| 48 |
+
|
| 49 |
+
# Documentation
|
| 50 |
+
**/docs
|
| 51 |
+
**/README.md
|
| 52 |
+
**/samples
|
| 53 |
+
**/hack
|
| 54 |
+
|
| 55 |
+
# Test files
|
| 56 |
+
**/*.test.js
|
| 57 |
+
**/*.spec.js
|
| 58 |
+
**/test
|
| 59 |
+
|
| 60 |
+
# Development only files
|
| 61 |
+
**/nodemon.json
|
| 62 |
+
**/.prettierrc
|
| 63 |
+
**/.eslintrc*
|
| 64 |
+
|
| 65 |
+
# Python virtual environment (if any)
|
| 66 |
+
**/venv
|
| 67 |
+
**/__pycache__
|
crossword-app/Dockerfile
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use Node.js 18 as base image
|
| 2 |
+
FROM node:18-alpine
|
| 3 |
+
|
| 4 |
+
# Set working directory
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Copy package files for both frontend and backend
|
| 8 |
+
COPY frontend/package*.json ./frontend/
|
| 9 |
+
COPY backend/package*.json ./backend/
|
| 10 |
+
|
| 11 |
+
# Install dependencies for both frontend and backend
|
| 12 |
+
RUN cd frontend && npm ci
|
| 13 |
+
RUN cd backend && npm ci --only=production
|
| 14 |
+
|
| 15 |
+
# Copy source code
|
| 16 |
+
COPY frontend/ ./frontend/
|
| 17 |
+
COPY backend/ ./backend/
|
| 18 |
+
|
| 19 |
+
# Build the React frontend
|
| 20 |
+
RUN cd frontend && npm run build
|
| 21 |
+
|
| 22 |
+
# Copy built frontend files to backend public directory
|
| 23 |
+
RUN mkdir -p backend/public && cp -r frontend/dist/* backend/public/
|
| 24 |
+
|
| 25 |
+
# Set working directory to backend for runtime
|
| 26 |
+
WORKDIR /app/backend
|
| 27 |
+
|
| 28 |
+
# Expose port 7860 (Hugging Face Spaces standard)
|
| 29 |
+
EXPOSE 7860
|
| 30 |
+
|
| 31 |
+
# Set environment to production
|
| 32 |
+
ENV NODE_ENV=production
|
| 33 |
+
ENV PORT=7860
|
| 34 |
+
|
| 35 |
+
# Start the backend server
|
| 36 |
+
CMD ["npm", "start"]
|
crossword-app/README.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Crossword Puzzle Generator
|
| 2 |
|
| 3 |
A full-stack web application that generates custom crossword puzzles based on selected topics using React frontend and Node.js backend.
|
|
@@ -164,6 +174,42 @@ curl -X POST http://localhost:3000/api/generate \
|
|
| 164 |
|
| 165 |
## Deployment
|
| 166 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
### Frontend (Vercel/Netlify)
|
| 168 |
```bash
|
| 169 |
cd frontend
|
|
@@ -181,9 +227,9 @@ cd backend
|
|
| 181 |
### Environment Variables for Production
|
| 182 |
```bash
|
| 183 |
NODE_ENV=production
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
```
|
| 188 |
|
| 189 |
## Contributing
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Crossword Puzzle Generator
|
| 3 |
+
emoji: 🧩
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
license: mit
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
# Crossword Puzzle Generator
|
| 12 |
|
| 13 |
A full-stack web application that generates custom crossword puzzles based on selected topics using React frontend and Node.js backend.
|
|
|
|
| 174 |
|
| 175 |
## Deployment
|
| 176 |
|
| 177 |
+
### Docker (Hugging Face Spaces/Local)
|
| 178 |
+
|
| 179 |
+
**Build and run locally:**
|
| 180 |
+
```bash
|
| 181 |
+
# Build the Docker image
|
| 182 |
+
docker build -t crossword-app .
|
| 183 |
+
|
| 184 |
+
# Run the container (production mode)
|
| 185 |
+
docker run -p 7860:7860 -e NODE_ENV=production crossword-app
|
| 186 |
+
|
| 187 |
+
# Or run in background
|
| 188 |
+
docker run -d -p 7860:7860 -e NODE_ENV=production --name crossword-app crossword-app
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
**Test the deployment:**
|
| 192 |
+
```bash
|
| 193 |
+
# Test API endpoint
|
| 194 |
+
curl http://localhost:7860/api/topics
|
| 195 |
+
|
| 196 |
+
# Test frontend (should return HTML)
|
| 197 |
+
curl http://localhost:7860/
|
| 198 |
+
|
| 199 |
+
# View container logs
|
| 200 |
+
docker logs crossword-app
|
| 201 |
+
|
| 202 |
+
# Stop and cleanup
|
| 203 |
+
docker stop crossword-app && docker rm crossword-app
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
The app will be available at http://localhost:7860
|
| 207 |
+
|
| 208 |
+
**For Hugging Face Spaces:**
|
| 209 |
+
1. Create a new Space with "Docker" SDK
|
| 210 |
+
2. Upload the entire `crossword-app/` directory
|
| 211 |
+
3. The container will build automatically and deploy on port 7860
|
| 212 |
+
|
| 213 |
### Frontend (Vercel/Netlify)
|
| 214 |
```bash
|
| 215 |
cd frontend
|
|
|
|
| 227 |
### Environment Variables for Production
|
| 228 |
```bash
|
| 229 |
NODE_ENV=production
|
| 230 |
+
PORT=7860
|
| 231 |
+
DATABASE_URL=postgresql://user:pass@host:port/db (optional)
|
| 232 |
+
CORS_ORIGIN=https://your-frontend-domain.com (for separate deployments)
|
| 233 |
```
|
| 234 |
|
| 235 |
## Contributing
|
crossword-app/backend/package.json
CHANGED
|
@@ -7,6 +7,8 @@
|
|
| 7 |
"scripts": {
|
| 8 |
"start": "node src/app.js",
|
| 9 |
"dev": "nodemon src/app.js",
|
|
|
|
|
|
|
| 10 |
"test": "jest",
|
| 11 |
"test:watch": "jest --watch",
|
| 12 |
"lint": "eslint src/",
|
|
|
|
| 7 |
"scripts": {
|
| 8 |
"start": "node src/app.js",
|
| 9 |
"dev": "nodemon src/app.js",
|
| 10 |
+
"build:frontend": "cd ../frontend && npm run build",
|
| 11 |
+
"build": "npm run build:frontend && mkdir -p public && cp -r ../frontend/dist/* public/",
|
| 12 |
"test": "jest",
|
| 13 |
"test:watch": "jest --watch",
|
| 14 |
"lint": "eslint src/",
|
crossword-app/backend/src/app.js
CHANGED
|
@@ -2,18 +2,31 @@ const express = require('express');
|
|
| 2 |
const cors = require('cors');
|
| 3 |
const helmet = require('helmet');
|
| 4 |
const rateLimit = require('express-rate-limit');
|
|
|
|
| 5 |
const apiRoutes = require('./routes/api');
|
| 6 |
|
| 7 |
const app = express();
|
| 8 |
-
const PORT = process.env.PORT ||
|
| 9 |
|
| 10 |
app.use(helmet());
|
| 11 |
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
app.use(cors(corsOptions));
|
| 18 |
|
| 19 |
const limiter = rateLimit({
|
|
@@ -42,19 +55,32 @@ app.use((req, res, next) => {
|
|
| 42 |
app.use('/api/generate', generateLimiter);
|
| 43 |
app.use('/api', apiRoutes);
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
words: 'GET /api/words/:topic',
|
| 54 |
-
health: 'GET /api/health'
|
| 55 |
-
}
|
| 56 |
});
|
| 57 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
app.use((req, res) => {
|
| 60 |
res.status(404).json({
|
|
@@ -87,7 +113,12 @@ if (require.main === module) {
|
|
| 87 |
app.listen(PORT, () => {
|
| 88 |
console.log(`Server running on port ${PORT}`);
|
| 89 |
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
});
|
| 92 |
}
|
| 93 |
|
|
|
|
| 2 |
const cors = require('cors');
|
| 3 |
const helmet = require('helmet');
|
| 4 |
const rateLimit = require('express-rate-limit');
|
| 5 |
+
const path = require('path');
|
| 6 |
const apiRoutes = require('./routes/api');
|
| 7 |
|
| 8 |
const app = express();
|
| 9 |
+
const PORT = process.env.PORT || 7860;
|
| 10 |
|
| 11 |
app.use(helmet());
|
| 12 |
|
| 13 |
+
// CORS configuration
|
| 14 |
+
let corsOptions;
|
| 15 |
+
if (process.env.NODE_ENV === 'production') {
|
| 16 |
+
// In production, allow same origin (since frontend is served from same server)
|
| 17 |
+
corsOptions = {
|
| 18 |
+
origin: true,
|
| 19 |
+
credentials: true,
|
| 20 |
+
optionsSuccessStatus: 200
|
| 21 |
+
};
|
| 22 |
+
} else {
|
| 23 |
+
// Development mode - allow dev servers
|
| 24 |
+
corsOptions = {
|
| 25 |
+
origin: process.env.CORS_ORIGIN || ['http://localhost:5173', 'http://localhost:3000'],
|
| 26 |
+
credentials: true,
|
| 27 |
+
optionsSuccessStatus: 200
|
| 28 |
+
};
|
| 29 |
+
}
|
| 30 |
app.use(cors(corsOptions));
|
| 31 |
|
| 32 |
const limiter = rateLimit({
|
|
|
|
| 55 |
app.use('/api/generate', generateLimiter);
|
| 56 |
app.use('/api', apiRoutes);
|
| 57 |
|
| 58 |
+
// Serve static files in production
|
| 59 |
+
if (process.env.NODE_ENV === 'production') {
|
| 60 |
+
const staticPath = path.join(__dirname, '../public');
|
| 61 |
+
app.use(express.static(staticPath));
|
| 62 |
+
|
| 63 |
+
// Handle React Router routes - serve index.html for non-API routes
|
| 64 |
+
app.get('*', (req, res) => {
|
| 65 |
+
res.sendFile(path.join(staticPath, 'index.html'));
|
|
|
|
|
|
|
|
|
|
| 66 |
});
|
| 67 |
+
} else {
|
| 68 |
+
// Development mode - show API info
|
| 69 |
+
app.get('/', (req, res) => {
|
| 70 |
+
res.json({
|
| 71 |
+
message: 'Crossword Puzzle API',
|
| 72 |
+
version: '1.0.0',
|
| 73 |
+
mode: 'development',
|
| 74 |
+
endpoints: {
|
| 75 |
+
topics: 'GET /api/topics',
|
| 76 |
+
generate: 'POST /api/generate',
|
| 77 |
+
validate: 'POST /api/validate',
|
| 78 |
+
words: 'GET /api/words/:topic',
|
| 79 |
+
health: 'GET /api/health'
|
| 80 |
+
}
|
| 81 |
+
});
|
| 82 |
+
});
|
| 83 |
+
}
|
| 84 |
|
| 85 |
app.use((req, res) => {
|
| 86 |
res.status(404).json({
|
|
|
|
| 113 |
app.listen(PORT, () => {
|
| 114 |
console.log(`Server running on port ${PORT}`);
|
| 115 |
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
|
| 116 |
+
if (process.env.NODE_ENV === 'production') {
|
| 117 |
+
console.log(`Serving React app from /public directory`);
|
| 118 |
+
console.log(`CORS enabled for same origin`);
|
| 119 |
+
} else {
|
| 120 |
+
console.log(`CORS enabled for: ${JSON.stringify(corsOptions.origin)}`);
|
| 121 |
+
}
|
| 122 |
});
|
| 123 |
}
|
| 124 |
|
crossword-app/frontend/src/hooks/useCrossword.js
CHANGED
|
@@ -6,7 +6,7 @@ const useCrossword = () => {
|
|
| 6 |
const [error, setError] = useState(null);
|
| 7 |
const [topics, setTopics] = useState([]);
|
| 8 |
|
| 9 |
-
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000';
|
| 10 |
|
| 11 |
const fetchTopics = useCallback(async () => {
|
| 12 |
try {
|
|
|
|
| 6 |
const [error, setError] = useState(null);
|
| 7 |
const [topics, setTopics] = useState([]);
|
| 8 |
|
| 9 |
+
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || (import.meta.env.PROD ? '' : 'http://localhost:3000');
|
| 10 |
|
| 11 |
const fetchTopics = useCallback(async () => {
|
| 12 |
try {
|