test first commit

This commit is contained in:
2026-02-06 16:48:34 +00:00
commit e6b5b1f3f8
5 changed files with 5781 additions and 0 deletions

39
Dockerfile Normal file
View File

@@ -0,0 +1,39 @@
# Multi-stage build for backend service
# Stage 1: Builder
FROM node:20-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Stage 2: Production
FROM node:20-alpine
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# Copy dependencies from builder
COPY --from=builder /app/node_modules ./node_modules
# Copy application code
COPY --chown=nodejs:nodejs src ./src
# Switch to non-root user
USER nodejs
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
# Start application
CMD ["node", "src/server.js"]

158
README.md Normal file
View File

@@ -0,0 +1,158 @@
# Inventory Backend Service
Minimal Node.js/Express backend for inventory management. Focus: **DevOps/CI-CD demo**, not application complexity.
## Features (Minimal by Design)
- 4 API endpoints: GET/POST items, health check, readiness check
- MySQL database with single table (4 fields)
- Input validation
- CORS and security headers
- Non-root Docker container
## Tech Stack
- Node.js 20
- Express 4
- MySQL 8.0
- mysql2 (prepared statements for SQL injection protection)
- helmet (security headers)
- express-validator
## Local Development
### Prerequisites
- Node.js 20+
- MySQL 8.0 running locally or via Docker
### Quick Start
```bash
# 1. Start MySQL
docker run -d \
--name mysql-local \
-e MYSQL_ROOT_PASSWORD=rootpass \
-e MYSQL_DATABASE=inventory \
-p 3306:3306 \
mysql:8.0
# 2. Install dependencies
npm install
# 3. Create .env file
cp .env.example .env
# 4. Start server
npm start
# Or with hot-reload:
npm run dev
```
### Testing
```bash
# Run tests
npm test
# Run linter
npm run lint
```
### API Endpoints
#### Health Check (Liveness)
```bash
curl http://localhost:3000/health
# Response: {"status": "ok"}
```
#### Readiness Check
```bash
curl http://localhost:3000/ready
# Response: {"status": "ready", "database": "connected"}
```
#### Get All Items
```bash
curl http://localhost:3000/api/items
# Response: [{"id":1,"name":"Laptop","quantity":10,"price":"999.99"},...]
```
#### Create Item
```bash
curl -X POST http://localhost:3000/api/items \
-H "Content-Type: application/json" \
-d '{"name":"Monitor","quantity":15,"price":299.99}'
# Response: {"id":4,"name":"Monitor","quantity":15,"price":299.99,"message":"Item created successfully"}
```
## Docker Build
```bash
# Build image
docker build -t backend-service:latest .
# Run container
docker run -d \
-p 3000:3000 \
-e DB_HOST=host.docker.internal \
-e DB_PASSWORD=rootpass \
backend-service:latest
```
## CI/CD Pipeline
Gitea Actions workflow:
1. Checkout → Install → Lint → Test
2. SonarQube scan + quality gate
3. Build multi-stage Docker image
4. Push to Gitea registry with tags: `{branch}-{sha}`, `{branch}`, `latest`
5. Update k8s-manifests repo
## Database Schema
```sql
CREATE TABLE items (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
quantity INT NOT NULL DEFAULT 0,
price DECIMAL(10, 2) NOT NULL
);
```
**That's it!** No timestamps, no foreign keys, no complexity. Focus on CI/CD.
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| PORT | 3000 | Server port |
| DB_HOST | localhost | MySQL host |
| DB_PORT | 3306 | MySQL port |
| DB_USER | root | MySQL user |
| DB_PASSWORD | rootpass | MySQL password |
| DB_NAME | inventory | Database name |
## Security
- ✓ Non-root Docker user (nodejs:1001)
- ✓ Helmet.js security headers
- ✓ SQL injection protection (prepared statements)
- ✓ Input validation (express-validator)
- ✓ CORS configured
- ✓ Multi-stage Docker build (no dev dependencies in prod)
## Project Structure
```
backend-service/
├── src/
│ ├── server.js # Main Express app (~100 lines, all routes inline)
│ ├── db.js # MySQL connection pool
│ └── init.sql # Database schema
├── tests/
│ └── server.test.js # Basic API tests
├── .gitea/workflows/
│ └── ci.yaml # CI/CD pipeline
├── Dockerfile # Multi-stage build
├── package.json
└── README.md
```
**Total: ~10 files** - intentionally minimal!

17
jest.config.js Normal file
View File

@@ -0,0 +1,17 @@
module.exports = {
testEnvironment: 'node',
coverageDirectory: 'coverage',
collectCoverageFrom: [
'src/**/*.js',
'!src/init.sql'
],
testMatch: ['**/tests/**/*.test.js'],
coverageThreshold: {
global: {
branches: 50,
functions: 50,
lines: 50,
statements: 50
}
}
};

5538
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "backend-service",
"version": "1.0.0",
"description": "Minimal inventory management backend for DevOps demo",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js",
"test": "jest --coverage",
"lint": "eslint src/"
},
"keywords": ["inventory", "express", "mysql"],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.18.2",
"mysql2": "^3.6.5",
"cors": "^2.8.5",
"helmet": "^7.1.0",
"dotenv": "^16.3.1",
"express-validator": "^7.0.1"
},
"devDependencies": {
"nodemon": "^3.0.2",
"jest": "^29.7.0",
"supertest": "^6.3.3",
"eslint": "^8.55.0"
}
}