@programinglive/dev-workflow-mcp-server 1.7.13 → 1.7.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/cloudflared-config.yml +3 -0
- package/docker-compose.yml +17 -0
- package/docs/release-notes/RELEASE_NOTES.md +38 -0
- package/netlify.toml +7 -0
- package/nginx-api.conf +27 -0
- package/package.json +1 -1
- package/scripts/test-auth-flow.js +75 -0
- package/web/Dockerfile.api +25 -0
- package/web/api-server.js +211 -0
- package/web/app/history/page.tsx +25 -18
- package/web/app/login/page.tsx +12 -14
- package/web/components/Hero.tsx +1 -1
- package/web/components/History.tsx +4 -4
- package/web/components/Navbar.tsx +11 -12
- package/web/lib/api-client.ts +123 -0
- package/web/next.config.ts +1 -2
- package/web/package-lock.json +820 -23
- package/web/package.json +8 -1
- package/scripts/add-user.js +0 -52
- package/scripts/health-check.js +0 -50
- package/scripts/test-history.js +0 -56
- package/web/app/api/history-fallback/route.ts +0 -56
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [1.7.14](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.13...v1.7.14) (2025-12-19)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ✨ Features
|
|
9
|
+
|
|
10
|
+
* add API dependencies and update login to use API client ([94bb42f](https://github.com/programinglive/dev-workflow-mcp-server/commit/94bb42f27b4bd225bb3813b81d5f2bff973edb16))
|
|
11
|
+
* add REST API server for frontend database access ([cdb347c](https://github.com/programinglive/dev-workflow-mcp-server/commit/cdb347cf204ce423b94db7e72980783e986a124d))
|
|
12
|
+
* implement JWT authentication and update client ([8efbe80](https://github.com/programinglive/dev-workflow-mcp-server/commit/8efbe80c3a69046f716b247fc359158dc8fec3e2))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### ♻️ Refactors
|
|
16
|
+
|
|
17
|
+
* remove session cookies, enforce JWT only ([bacd83a](https://github.com/programinglive/dev-workflow-mcp-server/commit/bacd83a16510499667143f4906d0cc8de0127d61))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### 🐛 Bug Fixes
|
|
21
|
+
|
|
22
|
+
* add credentials:include to getCurrentUser to persist session ([b8f2df5](https://github.com/programinglive/dev-workflow-mcp-server/commit/b8f2df531a2d2bae78ad3c3ea2c7dcda62e5e835))
|
|
23
|
+
* add root netlify.toml for monorepo support ([54c5ed3](https://github.com/programinglive/dev-workflow-mcp-server/commit/54c5ed3960aafd5a814773efa487efe9197d4385))
|
|
24
|
+
* enable trust proxy and force secure cookies for cross-domain session ([2c5d6e8](https://github.com/programinglive/dev-workflow-mcp-server/commit/2c5d6e858d05de06304d88286b931b1f12a95bf1))
|
|
25
|
+
* ensure getMe uses full API URL ([b3bd0da](https://github.com/programinglive/dev-workflow-mcp-server/commit/b3bd0da097de57b42d9253615f791677011486af))
|
|
26
|
+
* force API URL to tunnel address to resolve cached old IP ([1883731](https://github.com/programinglive/dev-workflow-mcp-server/commit/18837313a7b96395f1cf04b56d7fe1a74e13b1ed))
|
|
27
|
+
* inline auth functions in API server ([eefd73c](https://github.com/programinglive/dev-workflow-mcp-server/commit/eefd73c82dcf47d419662eaeb8854f6047e6464a))
|
|
28
|
+
* remove duplicate variable declarations in API server ([31d3621](https://github.com/programinglive/dev-workflow-mcp-server/commit/31d3621213ddb3560ee0570fdf6411d9acf7018f))
|
|
29
|
+
* remove dynamic API route for static export ([05a4dd0](https://github.com/programinglive/dev-workflow-mcp-server/commit/05a4dd0e4901e5ac329e50ebe392839d1242de4e))
|
|
30
|
+
* remove explicit publish path to fix netlify build ([cd64bcf](https://github.com/programinglive/dev-workflow-mcp-server/commit/cd64bcf0b4effb71be090235357d665d1f26b567))
|
|
31
|
+
* remove incorrect netlify publish dir for dynamic build ([ea34cf4](https://github.com/programinglive/dev-workflow-mcp-server/commit/ea34cf464466b4ddc8eaa6432434d1fbabb3583e))
|
|
32
|
+
* remove incorrect publish dir from netlify config ([010236f](https://github.com/programinglive/dev-workflow-mcp-server/commit/010236fd0eb1419f7cae4429159f4aac1a988174))
|
|
33
|
+
* restore static export for netlify deployment ([685d841](https://github.com/programinglive/dev-workflow-mcp-server/commit/685d84178b239b5cd681b4d479456e69519a8af8))
|
|
34
|
+
* update API URL to use Cloudflare Tunnel for HTTPS ([ab72f6c](https://github.com/programinglive/dev-workflow-mcp-server/commit/ab72f6c0b8acc6974aba7c8df8418e35da6f2a51))
|
|
35
|
+
* use apiClient in history page and add version indicator ([4889669](https://github.com/programinglive/dev-workflow-mcp-server/commit/48896694166cd5d42ab4134d4dbfdb0df8ca0d44))
|
|
36
|
+
* use npm install instead of ci to avoid lockfile issues ([e5fae26](https://github.com/programinglive/dev-workflow-mcp-server/commit/e5fae26f767c5f0e4aba2593df321c700ac14a38))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### 🧹 Chores
|
|
40
|
+
|
|
41
|
+
* bump version to 1.7.14 to ensure fresh build ([f7d8886](https://github.com/programinglive/dev-workflow-mcp-server/commit/f7d88861ceb111c57e3b34bf4b5a5cc25a19805b))
|
|
42
|
+
* bump version to 1.7.15 for JWT frontend deploy ([7850268](https://github.com/programinglive/dev-workflow-mcp-server/commit/7850268d019251619d60a0a841db2bab8061be04))
|
|
43
|
+
* cleanup post-release and update website version to 1.7.13 ([7341d44](https://github.com/programinglive/dev-workflow-mcp-server/commit/7341d446bbddcb9232e70606acaf950fde7ff5c2))
|
|
44
|
+
* save pending changes before release ([4d4b1c9](https://github.com/programinglive/dev-workflow-mcp-server/commit/4d4b1c9e6122baed3cc6782a572fe3700dabf19b))
|
|
45
|
+
|
|
5
46
|
### [1.7.13](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.12...v1.7.13) (2025-12-18)
|
|
6
47
|
|
|
7
48
|
|
package/docker-compose.yml
CHANGED
|
@@ -19,6 +19,23 @@ services:
|
|
|
19
19
|
timeout: 5s
|
|
20
20
|
retries: 5
|
|
21
21
|
|
|
22
|
+
api:
|
|
23
|
+
build:
|
|
24
|
+
context: ./web
|
|
25
|
+
dockerfile: Dockerfile.api
|
|
26
|
+
container_name: dev-workflow-api
|
|
27
|
+
ports:
|
|
28
|
+
- "8080:8080"
|
|
29
|
+
environment:
|
|
30
|
+
- DEV_WORKFLOW_DB_URL=${DEV_WORKFLOW_DB_URL}
|
|
31
|
+
- SESSION_SECRET=${SESSION_SECRET:-dev-workflow-secret-change-in-production}
|
|
32
|
+
- NODE_ENV=${NODE_ENV:-production}
|
|
33
|
+
- API_PORT=8080
|
|
34
|
+
depends_on:
|
|
35
|
+
postgres:
|
|
36
|
+
condition: service_healthy
|
|
37
|
+
restart: unless-stopped
|
|
38
|
+
|
|
22
39
|
volumes:
|
|
23
40
|
postgres_data:
|
|
24
41
|
driver: local
|
|
@@ -4,6 +4,8 @@ Authoritative history of user-visible changes for the Development Workflow MCP S
|
|
|
4
4
|
|
|
5
5
|
| Version | Date | Notes |
|
|
6
6
|
|---------|------|-------|
|
|
7
|
+
| 1.7.14 | 2025-12-19 | add API dependencies and update login to use API client (94bb42f) |
|
|
8
|
+
| 1.7.13 | 2025-12-18 | remote db connection, history api 500 errors, and windows startup (ce559a6) |
|
|
7
9
|
| 1.7.12 | 2025-12-17 | post-release cleanup v1.7.11 (c2ee733) |
|
|
8
10
|
| 1.7.11 | 2025-12-17 | test: comprehensive DB adapter tests, env support, and warning fixes (64b029a) |
|
|
9
11
|
| 1.7.10 | 2025-12-16 | update release notes and finalize ci config (92ac78a) |
|
|
@@ -37,6 +39,42 @@ Authoritative history of user-visible changes for the Development Workflow MCP S
|
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## 1.7.14 – ✨ Features
|
|
45
|
+
|
|
46
|
+
Released on **2025-12-19**.
|
|
47
|
+
|
|
48
|
+
- add API dependencies and update login to use API client (94bb42f)
|
|
49
|
+
- add REST API server for frontend database access (cdb347c)
|
|
50
|
+
- implement JWT authentication and update client (8efbe80)
|
|
51
|
+
- remove session cookies, enforce JWT only (bacd83a)
|
|
52
|
+
- add credentials:include to getCurrentUser to persist session (b8f2df5)
|
|
53
|
+
- add root netlify.toml for monorepo support (54c5ed3)
|
|
54
|
+
- enable trust proxy and force secure cookies for cross-domain session (2c5d6e8)
|
|
55
|
+
- ensure getMe uses full API URL (b3bd0da)
|
|
56
|
+
- force API URL to tunnel address to resolve cached old IP (1883731)
|
|
57
|
+
- inline auth functions in API server (eefd73c)
|
|
58
|
+
- remove duplicate variable declarations in API server (31d3621)
|
|
59
|
+
- remove dynamic API route for static export (05a4dd0)
|
|
60
|
+
- remove explicit publish path to fix netlify build (cd64bcf)
|
|
61
|
+
- remove incorrect netlify publish dir for dynamic build (ea34cf4)
|
|
62
|
+
- remove incorrect publish dir from netlify config (010236f)
|
|
63
|
+
- restore static export for netlify deployment (685d841)
|
|
64
|
+
- update API URL to use Cloudflare Tunnel for HTTPS (ab72f6c)
|
|
65
|
+
- use apiClient in history page and add version indicator (4889669)
|
|
66
|
+
- use npm install instead of ci to avoid lockfile issues (e5fae26)
|
|
67
|
+
- bump version to 1.7.14 to ensure fresh build (f7d8886)
|
|
68
|
+
- bump version to 1.7.15 for JWT frontend deploy (7850268)
|
|
69
|
+
- cleanup post-release and update website version to 1.7.13 (7341d44)
|
|
70
|
+
- save pending changes before release (4d4b1c9)
|
|
71
|
+
|
|
72
|
+
## 1.7.13 – 🐛 Bug Fixes
|
|
73
|
+
|
|
74
|
+
Released on **2025-12-18**.
|
|
75
|
+
|
|
76
|
+
- remote db connection, history api 500 errors, and windows startup (ce559a6)
|
|
77
|
+
|
|
40
78
|
## 1.7.12 – 📝 Documentation
|
|
41
79
|
|
|
42
80
|
Released on **2025-12-17**.
|
package/netlify.toml
ADDED
package/nginx-api.conf
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
server {
|
|
2
|
+
listen 80;
|
|
3
|
+
server_name api.devworkflow.programinglive.com;
|
|
4
|
+
|
|
5
|
+
location / {
|
|
6
|
+
proxy_pass http://localhost:8080;
|
|
7
|
+
proxy_http_version 1.1;
|
|
8
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
9
|
+
proxy_set_header Connection 'upgrade';
|
|
10
|
+
proxy_set_header Host $host;
|
|
11
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
12
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
13
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
14
|
+
proxy_cache_bypass $http_upgrade;
|
|
15
|
+
|
|
16
|
+
# CORS headers (backup in case API doesn't set them)
|
|
17
|
+
add_header 'Access-Control-Allow-Origin' 'https://devworkflow.programinglive.com' always;
|
|
18
|
+
add_header 'Access-Control-Allow-Credentials' 'true' always;
|
|
19
|
+
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
|
|
20
|
+
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
|
|
21
|
+
|
|
22
|
+
# Handle preflight requests
|
|
23
|
+
if ($request_method = 'OPTIONS') {
|
|
24
|
+
return 204;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@programinglive/dev-workflow-mcp-server",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.14",
|
|
4
4
|
"description": "MCP server to enforce development workflow discipline",
|
|
5
5
|
"homepage": "https://devworkflow.programinglive.com/",
|
|
6
6
|
"mcpName": "io.github.programinglive/dev-workflow-mcp-server",
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
|
|
3
|
+
const API_BASE_URL = 'https://hollywood-andrew-cycle-genre.trycloudflare.com';
|
|
4
|
+
const USERNAME = 'programinglive';
|
|
5
|
+
const PASSWORD = 'mcpdevserver';
|
|
6
|
+
|
|
7
|
+
async function testAuthFlow() {
|
|
8
|
+
console.log('🚀 Starting Auth Flow Test...');
|
|
9
|
+
console.log(`Target: ${API_BASE_URL}\n`);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// 1. Login
|
|
13
|
+
console.log('1️⃣ Attempting Login...');
|
|
14
|
+
const loginRes = await fetch(`${API_BASE_URL}/api/auth/login`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17
|
+
body: JSON.stringify({ username: USERNAME, password: PASSWORD }),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
console.log(` Status: ${loginRes.status}`);
|
|
21
|
+
if (!loginRes.ok) {
|
|
22
|
+
throw new Error(`Login failed: ${loginRes.statusText}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const loginData = await loginRes.json();
|
|
26
|
+
const token = loginData.token;
|
|
27
|
+
|
|
28
|
+
if (token) {
|
|
29
|
+
console.log(' ✅ Login Successful! JWT Token received.');
|
|
30
|
+
console.log(` Token: ${token.substring(0, 20)}...`);
|
|
31
|
+
} else {
|
|
32
|
+
console.log(' ⚠️ Login successful but NO TOKEN returned (Legacy Session mode?)');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Access Protected Route (GET /me)
|
|
36
|
+
console.log('\n2️⃣ Verifying Session/Token (GET /api/auth/me)...');
|
|
37
|
+
const headers = {};
|
|
38
|
+
if (token) {
|
|
39
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
40
|
+
console.log(' Using Authorization: Bearer header');
|
|
41
|
+
} else {
|
|
42
|
+
console.log(' Using Cookies (if any)');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const meRes = await fetch(`${API_BASE_URL}/api/auth/me`, {
|
|
46
|
+
headers,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log(` Status: ${meRes.status}`);
|
|
50
|
+
if (meRes.ok) {
|
|
51
|
+
const meData = await meRes.json();
|
|
52
|
+
console.log(` ✅ Authorized! User: ${meData.username} (${meData.email})`);
|
|
53
|
+
} else {
|
|
54
|
+
console.error(' ❌ Authorization Failed!');
|
|
55
|
+
console.error(await meRes.text());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 3. Access History (GET /api/history)
|
|
59
|
+
console.log('\n3️⃣ Fetching History...');
|
|
60
|
+
const historyRes = await fetch(`${API_BASE_URL}/api/history`, { headers });
|
|
61
|
+
console.log(` Status: ${historyRes.status}`);
|
|
62
|
+
|
|
63
|
+
if (historyRes.ok) {
|
|
64
|
+
const historyData = await historyRes.json();
|
|
65
|
+
console.log(` ✅ History fetched! Count: ${historyData.history ? historyData.history.length : 0}`);
|
|
66
|
+
} else {
|
|
67
|
+
console.log(' ❌ History fetch failed.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('\n❌ Test Failed:', error.message);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
testAuthFlow();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
FROM node:20-alpine
|
|
2
|
+
|
|
3
|
+
# Install dependencies for native modules
|
|
4
|
+
RUN apk add --no-cache python3 make g++ postgresql-client
|
|
5
|
+
|
|
6
|
+
WORKDIR /app
|
|
7
|
+
|
|
8
|
+
# Copy package files
|
|
9
|
+
COPY package*.json ./
|
|
10
|
+
|
|
11
|
+
# Install dependencies
|
|
12
|
+
RUN npm install --only=production
|
|
13
|
+
|
|
14
|
+
# Copy application code
|
|
15
|
+
COPY . .
|
|
16
|
+
|
|
17
|
+
# Expose API port
|
|
18
|
+
EXPOSE 8080
|
|
19
|
+
|
|
20
|
+
# Set environment
|
|
21
|
+
ENV NODE_ENV=production
|
|
22
|
+
ENV API_PORT=8080
|
|
23
|
+
|
|
24
|
+
# Run API server
|
|
25
|
+
CMD ["node", "api-server.js"]
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import pg from 'pg';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
import bcrypt from 'bcrypt';
|
|
6
|
+
import jwt from 'jsonwebtoken';
|
|
7
|
+
|
|
8
|
+
dotenv.config({ path: '../.env' });
|
|
9
|
+
|
|
10
|
+
const app = express();
|
|
11
|
+
const { Pool } = pg;
|
|
12
|
+
|
|
13
|
+
const PORT = process.env.API_PORT || 8080;
|
|
14
|
+
const dbUrl = process.env.DEV_WORKFLOW_DB_URL;
|
|
15
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'dev-workflow-jwt-secret-change-me';
|
|
16
|
+
|
|
17
|
+
if (!dbUrl) {
|
|
18
|
+
console.error('❌ DEV_WORKFLOW_DB_URL is not set');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pool = new Pool({ connectionString: dbUrl });
|
|
23
|
+
|
|
24
|
+
// Authentication helper functions
|
|
25
|
+
async function authenticateUser(pool, username, password) {
|
|
26
|
+
const result = await pool.query(
|
|
27
|
+
'SELECT * FROM users WHERE username = $1',
|
|
28
|
+
[username]
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
if (result.rows.length === 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const user = result.rows[0];
|
|
36
|
+
const isValid = await bcrypt.compare(password, user.password_hash);
|
|
37
|
+
|
|
38
|
+
if (!isValid) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
id: user.id,
|
|
44
|
+
username: user.username,
|
|
45
|
+
email: user.email,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function getUserById(pool, userId) {
|
|
50
|
+
const result = await pool.query(
|
|
51
|
+
'SELECT id, username, email FROM users WHERE id = $1',
|
|
52
|
+
[userId]
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return result.rows[0] || null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// CORS configuration - allow Netlify domain
|
|
59
|
+
const corsOptions = {
|
|
60
|
+
origin: [
|
|
61
|
+
'https://devworkflow.programinglive.com',
|
|
62
|
+
'http://localhost:3000',
|
|
63
|
+
],
|
|
64
|
+
credentials: true,
|
|
65
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
66
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
app.use(cors(corsOptions));
|
|
70
|
+
app.use(express.json());
|
|
71
|
+
app.use(express.urlencoded({ extended: true }));
|
|
72
|
+
app.set('trust proxy', 1);
|
|
73
|
+
|
|
74
|
+
// Middleware to verify JWT
|
|
75
|
+
const requireAuth = (req, res, next) => {
|
|
76
|
+
const authHeader = req.headers.authorization;
|
|
77
|
+
|
|
78
|
+
if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
79
|
+
const token = authHeader.split(' ')[1];
|
|
80
|
+
try {
|
|
81
|
+
const decoded = jwt.verify(token, JWT_SECRET);
|
|
82
|
+
req.user = decoded;
|
|
83
|
+
return next();
|
|
84
|
+
} catch (err) {
|
|
85
|
+
return res.status(401).json({ error: 'Invalid token' });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return res.status(401).json({ error: 'Authentication required' });
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Health check endpoint
|
|
93
|
+
app.get('/api/health', (req, res) => {
|
|
94
|
+
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Authentication endpoints
|
|
98
|
+
app.post('/api/auth/login', async (req, res) => {
|
|
99
|
+
try {
|
|
100
|
+
const { username, password } = req.body;
|
|
101
|
+
|
|
102
|
+
if (!username || !password) {
|
|
103
|
+
return res.status(400).json({ error: 'Username and password are required' });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const user = await authenticateUser(pool, username, password);
|
|
107
|
+
|
|
108
|
+
if (!user) {
|
|
109
|
+
return res.status(401).json({ error: 'Invalid credentials' });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Generate JWT Token
|
|
113
|
+
const token = jwt.sign(
|
|
114
|
+
{ userId: user.id, username: user.username },
|
|
115
|
+
JWT_SECRET,
|
|
116
|
+
{ expiresIn: '30d' }
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
res.json({
|
|
120
|
+
success: true,
|
|
121
|
+
token,
|
|
122
|
+
user: {
|
|
123
|
+
id: user.id,
|
|
124
|
+
username: user.username,
|
|
125
|
+
email: user.email,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Login error:', error);
|
|
130
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
app.post('/api/auth/logout', (req, res) => {
|
|
135
|
+
// Client-side only action for JWT
|
|
136
|
+
res.json({ success: true });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
app.get('/api/auth/me', requireAuth, async (req, res) => {
|
|
140
|
+
try {
|
|
141
|
+
const user = await getUserById(pool, req.user.userId);
|
|
142
|
+
|
|
143
|
+
if (!user) {
|
|
144
|
+
return res.status(404).json({ error: 'User not found' });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
res.json({
|
|
148
|
+
id: user.id,
|
|
149
|
+
username: user.username,
|
|
150
|
+
email: user.email,
|
|
151
|
+
});
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error('Get user error:', error);
|
|
154
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Workflow history endpoints
|
|
159
|
+
app.get('/api/history', requireAuth, async (req, res) => {
|
|
160
|
+
try {
|
|
161
|
+
const result = await pool.query(
|
|
162
|
+
`SELECT id, task_type, description, commit_message, completed_at, tests_passed, documentation_type
|
|
163
|
+
FROM workflow_history
|
|
164
|
+
WHERE user_id = $1
|
|
165
|
+
ORDER BY completed_at DESC
|
|
166
|
+
LIMIT 100`,
|
|
167
|
+
[req.user.userId]
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
res.json({
|
|
171
|
+
history: result.rows,
|
|
172
|
+
});
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error('Get history error:', error);
|
|
175
|
+
res.status(500).json({ error: 'Failed to fetch history' });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
app.post('/api/history', requireAuth, async (req, res) => {
|
|
180
|
+
try {
|
|
181
|
+
const { task_type, description, commit_message, tests_passed, documentation_type } = req.body;
|
|
182
|
+
|
|
183
|
+
const result = await pool.query(
|
|
184
|
+
`INSERT INTO workflow_history (user_id, task_type, description, commit_message, tests_passed, documentation_type, completed_at)
|
|
185
|
+
VALUES ($1, $2, $3, $4, $5, $6, NOW())
|
|
186
|
+
RETURNING *`,
|
|
187
|
+
[req.user.userId, task_type, description, commit_message, tests_passed, documentation_type]
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
res.json({
|
|
191
|
+
success: true,
|
|
192
|
+
history: result.rows[0],
|
|
193
|
+
});
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error('Create history error:', error);
|
|
196
|
+
res.status(500).json({ error: 'Failed to create history entry' });
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Error handling middleware
|
|
201
|
+
app.use((err, req, res, next) => {
|
|
202
|
+
console.error('Unhandled error:', err);
|
|
203
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Start server
|
|
207
|
+
app.listen(PORT, '0.0.0.0', () => {
|
|
208
|
+
console.log(`✅ API server running on port ${PORT}`);
|
|
209
|
+
console.log(`📊 Database: ${dbUrl.replace(/:[^:]*@/, ':****@')}`);
|
|
210
|
+
console.log(`🌐 CORS enabled for: ${corsOptions.origin.join(', ')}`);
|
|
211
|
+
});
|
package/web/app/history/page.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState, useMemo } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { apiClient } from '@/lib/api-client';
|
|
5
6
|
import {
|
|
6
7
|
CheckCircle,
|
|
7
8
|
XCircle,
|
|
@@ -34,27 +35,33 @@ export default function HistoryPage() {
|
|
|
34
35
|
const router = useRouter();
|
|
35
36
|
|
|
36
37
|
useEffect(() => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
localStorage.setItem('isLoggedIn', 'true');
|
|
43
|
-
window.dispatchEvent(new Event('authUpdate'));
|
|
44
|
-
return fetch('/api/history-fallback');
|
|
45
|
-
} else {
|
|
46
|
-
router.push('/login');
|
|
38
|
+
const loadData = async () => {
|
|
39
|
+
try {
|
|
40
|
+
// Verify auth
|
|
41
|
+
const user = await apiClient.getCurrentUser();
|
|
42
|
+
if (!user) {
|
|
47
43
|
throw new Error('Not authenticated');
|
|
48
44
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
|
|
46
|
+
setIsAuthenticated(true);
|
|
47
|
+
// Sync auth state
|
|
48
|
+
localStorage.setItem('isLoggedIn', 'true');
|
|
49
|
+
window.dispatchEvent(new Event('authUpdate'));
|
|
50
|
+
|
|
51
|
+
// Fetch history
|
|
52
|
+
const result = await apiClient.getHistory();
|
|
53
|
+
if (result?.history) {
|
|
54
|
+
setHistory(result.history);
|
|
54
55
|
}
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('History load error:', error);
|
|
58
|
+
router.push('/login');
|
|
59
|
+
} finally {
|
|
60
|
+
setLoading(false);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
loadData();
|
|
58
65
|
}, [router]);
|
|
59
66
|
|
|
60
67
|
const stats = useMemo(() => {
|
package/web/app/login/page.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
|
|
4
4
|
import { useRouter } from 'next/navigation';
|
|
5
5
|
import Image from 'next/image';
|
|
6
6
|
import { Lock, User } from 'lucide-react';
|
|
7
|
+
import { apiClient } from '@/lib/api-client';
|
|
7
8
|
|
|
8
9
|
export default function LoginPage() {
|
|
9
10
|
const [username, setUsername] = useState('');
|
|
@@ -21,9 +22,9 @@ export default function LoginPage() {
|
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
.then(
|
|
26
|
-
if (
|
|
25
|
+
apiClient.getCurrentUser()
|
|
26
|
+
.then(user => {
|
|
27
|
+
if (user) {
|
|
27
28
|
router.push('/history');
|
|
28
29
|
}
|
|
29
30
|
})
|
|
@@ -37,23 +38,17 @@ export default function LoginPage() {
|
|
|
37
38
|
setLoading(true);
|
|
38
39
|
|
|
39
40
|
try {
|
|
40
|
-
const
|
|
41
|
-
method: 'POST',
|
|
42
|
-
headers: { 'Content-Type': 'application/json' },
|
|
43
|
-
body: JSON.stringify({ username, password }),
|
|
44
|
-
});
|
|
41
|
+
const data = await apiClient.login(username, password);
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (response.ok) {
|
|
43
|
+
if (data.success) {
|
|
49
44
|
localStorage.setItem('isLoggedIn', 'true');
|
|
50
45
|
window.dispatchEvent(new Event('authUpdate'));
|
|
51
46
|
router.push('/history');
|
|
52
47
|
} else {
|
|
53
|
-
setError(
|
|
48
|
+
setError('Login failed');
|
|
54
49
|
}
|
|
55
|
-
} catch (err) {
|
|
56
|
-
setError('Network error. Please try again.');
|
|
50
|
+
} catch (err: any) {
|
|
51
|
+
setError(err.message || 'Network error. Please try again.');
|
|
57
52
|
} finally {
|
|
58
53
|
setLoading(false);
|
|
59
54
|
}
|
|
@@ -77,6 +72,9 @@ export default function LoginPage() {
|
|
|
77
72
|
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
|
78
73
|
Sign in to access your workflow history
|
|
79
74
|
</p>
|
|
75
|
+
<p className="mt-1 text-xs text-gray-400 font-mono">
|
|
76
|
+
v1.7.18 (JWT Fix)
|
|
77
|
+
</p>
|
|
80
78
|
</div>
|
|
81
79
|
|
|
82
80
|
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
package/web/components/Hero.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
+
import { apiClient } from "@/lib/api-client";
|
|
4
5
|
|
|
5
6
|
type HistoryItem = {
|
|
6
7
|
id: number;
|
|
@@ -31,11 +32,10 @@ export default function History() {
|
|
|
31
32
|
|
|
32
33
|
const loadData = async () => {
|
|
33
34
|
try {
|
|
34
|
-
// Fetch workflow history from API
|
|
35
|
-
const
|
|
35
|
+
// Fetch workflow history from API via apiClient
|
|
36
|
+
const data = await apiClient.getHistory();
|
|
36
37
|
|
|
37
|
-
if (
|
|
38
|
-
const data = await response.json();
|
|
38
|
+
if (data?.history) {
|
|
39
39
|
setHistory(data.history || []);
|
|
40
40
|
setTotalPages(Math.ceil((data.history?.length || 0) / 10));
|
|
41
41
|
setSummary({
|