@programinglive/dev-workflow-mcp-server 1.7.10 → 1.7.12

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.
Files changed (39) hide show
  1. package/.dockerignore +60 -0
  2. package/.env.example +12 -0
  3. package/.env.production +16 -0
  4. package/CHANGELOG.md +45 -0
  5. package/Dockerfile +32 -0
  6. package/README.md +63 -2
  7. package/db/adapters/mysql.js +11 -4
  8. package/db/migrations/002_create_users_table.sql +22 -0
  9. package/db/migrations/003_create_workflow_history.sql +14 -0
  10. package/db/migrations/004_fix_workflow_history.sql +3 -0
  11. package/docker-compose.yml +24 -0
  12. package/docs/GCP_DEPLOYMENT.md +298 -0
  13. package/docs/product/PRD.md +5 -1
  14. package/docs/release-notes/RELEASE_NOTES.md +26 -0
  15. package/index.js +2 -0
  16. package/package.json +9 -3
  17. package/scripts/add-user.js +52 -0
  18. package/scripts/create-admin.js +47 -0
  19. package/scripts/deploy-gcloud.ps1 +160 -0
  20. package/scripts/deploy-gcloud.sh +154 -0
  21. package/tests/db-adapters.test.js +108 -0
  22. package/tests/env-support.test.js +46 -0
  23. package/tests/workflowState.test.js +16 -1
  24. package/web/__tests__/api/workflow-history.test.js +61 -0
  25. package/web/app/docs/page.tsx +1 -1
  26. package/web/app/history/page.tsx +213 -0
  27. package/web/app/layout.tsx +1 -1
  28. package/web/app/login/page.tsx +144 -0
  29. package/web/app/page.tsx +0 -2
  30. package/web/components/Hero.tsx +1 -1
  31. package/web/components/History.tsx +63 -16
  32. package/web/components/Navbar.tsx +111 -9
  33. package/web/lib/auth.js +75 -0
  34. package/web/middleware/auth.js +26 -0
  35. package/web/next.config.ts +4 -0
  36. package/web/package-lock.json +40 -40
  37. package/web/package.json +6 -5
  38. package/web/scripts/smoke-test.js +30 -0
  39. package/web/server.js +163 -18
package/web/server.js CHANGED
@@ -1,31 +1,176 @@
1
- const { createServer } = require('http');
2
- const { parse } = require('url');
3
- const next = require('next');
1
+ import { createServer } from 'http';
2
+ import { parse } from 'url';
3
+ import next from 'next';
4
+ import express from 'express';
5
+ import session from 'express-session';
6
+ import connectPgSimple from 'connect-pg-simple';
7
+ import pg from 'pg';
8
+
9
+ const pgSession = connectPgSimple(session);
10
+ const { Pool } = pg;
4
11
 
5
12
  const dev = process.env.NODE_ENV !== 'production';
6
13
  const hostname = 'localhost';
7
14
  const port = process.env.PORT || 3000;
8
15
 
16
+ // Initialize PostgreSQL pool for sessions
17
+ const dbUrl = process.env.DEV_WORKFLOW_DB_URL;
18
+ const pool = dbUrl ? new Pool({ connectionString: dbUrl }) : null;
19
+
9
20
  // Initialize Next.js app
10
21
  const app = next({ dev, hostname, port });
11
22
  const handle = app.getRequestHandler();
12
23
 
24
+ // Import auth utilities
25
+ let authenticateUser, getUserById;
26
+ if (pool) {
27
+ import('./lib/auth.js').then(module => {
28
+ authenticateUser = module.authenticateUser;
29
+ getUserById = module.getUserById;
30
+ });
31
+ }
32
+
13
33
  app.prepare().then(() => {
14
- createServer(async (req, res) => {
34
+ const server = express();
35
+
36
+ server.use(express.json());
37
+ server.use(express.urlencoded({ extended: true }));
38
+
39
+ // Session middleware
40
+ if (pool) {
41
+ server.use(
42
+ session({
43
+ store: new pgSession({
44
+ pool,
45
+ tableName: 'session'
46
+ }),
47
+ secret: process.env.SESSION_SECRET || 'dev-workflow-secret-change-in-production',
48
+ resave: false,
49
+ saveUninitialized: false,
50
+ cookie: {
51
+ maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
52
+ httpOnly: true,
53
+ secure: process.env.NODE_ENV === 'production'
54
+ }
55
+ })
56
+ );
57
+ }
58
+
59
+ // Auth API routes
60
+ server.post('/api/auth/login', async (req, res) => {
61
+ if (!pool || !authenticateUser) {
62
+ return res.status(500).json({ error: 'Database not configured' });
63
+ }
64
+
65
+ const { username, password } = req.body;
66
+
67
+ if (!username || !password) {
68
+ return res.status(400).json({ error: 'Username and password required' });
69
+ }
70
+
15
71
  try {
16
- const parsedUrl = parse(req.url, true);
17
- await handle(req, res, parsedUrl);
18
- } catch (err) {
19
- console.error('Error occurred handling', req.url, err);
20
- res.statusCode = 500;
21
- res.end('internal server error');
22
- }
23
- })
24
- .once('error', (err) => {
25
- console.error(err);
26
- process.exit(1);
27
- })
28
- .listen(port, () => {
29
- console.log(`> Ready on http://${hostname}:${port}`);
72
+ const user = await authenticateUser(pool, username, password);
73
+
74
+ if (!user) {
75
+ return res.status(401).json({ error: 'Invalid credentials' });
76
+ }
77
+
78
+ req.session.userId = user.id;
79
+ req.session.username = user.username;
80
+
81
+ res.json({ success: true, user: { username: user.username, email: user.email } });
82
+ } catch (error) {
83
+ console.error('Login error:', error);
84
+ res.status(500).json({ error: 'Internal server error' });
85
+ }
86
+ });
87
+
88
+ server.post('/api/auth/logout', (req, res) => {
89
+ req.session.destroy((err) => {
90
+ if (err) {
91
+ return res.status(500).json({ error: 'Logout failed' });
92
+ }
93
+ res.json({ success: true });
30
94
  });
95
+ });
96
+
97
+ server.get('/api/auth/me', async (req, res) => {
98
+ if (!req.session || !req.session.userId) {
99
+ return res.status(401).json({ error: 'Not authenticated' });
100
+ }
101
+
102
+ if (!pool || !getUserById) {
103
+ return res.status(500).json({ error: 'Database not configured' });
104
+ }
105
+
106
+ try {
107
+ const user = await getUserById(pool, req.session.userId);
108
+
109
+ if (!user) {
110
+ return res.status(404).json({ error: 'User not found' });
111
+ }
112
+
113
+ res.json({ user: { username: user.username, email: user.email } });
114
+ } catch (error) {
115
+ console.error('Get user error:', error);
116
+ res.status(500).json({ error: 'Internal server error' });
117
+ }
118
+ });
119
+
120
+ // Get workflow history
121
+ server.get('/api/workflow/history', async (req, res) => {
122
+ if (!req.session || !req.session.userId) {
123
+ return res.status(401).json({ error: 'Not authenticated' });
124
+ }
125
+
126
+ if (!pool) {
127
+ return res.status(500).json({ error: 'Database not configured' });
128
+ }
129
+
130
+ try {
131
+ // Get user details to filter history
132
+ // We assume workflow_history.user_id stores the username (based on schema)
133
+ const user = req.session.username; // This should be populated during login
134
+
135
+ let query = `
136
+ SELECT
137
+ id,
138
+ task_type,
139
+ task_description as description,
140
+ commit_message,
141
+ timestamp as completed_at,
142
+ tests_passed,
143
+ documentation_type
144
+ FROM workflow_history
145
+ `;
146
+
147
+ const params = [];
148
+
149
+ // If we have a username, filter by it.
150
+ // Note: DB schema has user_id just as text, assuming it matches username
151
+ if (user) {
152
+ query += ` WHERE user_id = $1 `;
153
+ params.push(user);
154
+ }
155
+
156
+ query += ` ORDER BY timestamp DESC LIMIT 100`;
157
+
158
+ const result = await pool.query(query, params);
159
+
160
+ res.json({ history: result.rows });
161
+ } catch (error) {
162
+ console.error('Get history error:', error);
163
+ res.status(500).json({ error: 'Internal server error' });
164
+ }
165
+ });
166
+
167
+ // Next.js handler for all other routes
168
+ server.all('*', (req, res) => {
169
+ return handle(req, res);
170
+ });
171
+
172
+ server.listen(port, (err) => {
173
+ if (err) throw err;
174
+ console.log(`> Ready on http://${hostname}:${port}`);
175
+ });
31
176
  });