Skip to content

Commit f2c64e1

Browse files
committed
fix(api): add missing routes/docs controllers to git
- Update .gitignore to allow routes/docs/ directory - Add all collaborative docs controller files - Fixes server error: Cannot find module 'routes/docs/index.js' Files added: - documentController.ts - exportController.ts - exportToDocsController.ts - index.ts (router) - permissionsController.ts - versionController.ts
1 parent 8f56314 commit f2c64e1

7 files changed

Lines changed: 1298 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ scripts/
8686
# Documentation (local development)
8787
docs/
8888
!apps/api/docs/
89+
!apps/api/routes/docs/
8990
!apps/docs/
9091

9192
# Backup files
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
import { Router, Request, Response } from 'express';
2+
import { getPostgresInstance } from '../../database/services/PostgresService/PostgresService.js';
3+
4+
const router = Router();
5+
const db = getPostgresInstance();
6+
7+
interface AuthenticatedRequest extends Request {
8+
user?: {
9+
id: string;
10+
email?: string;
11+
display_name?: string;
12+
};
13+
}
14+
15+
/**
16+
* @route POST /api/docs
17+
* @desc Create a new collaborative document
18+
* @access Private
19+
*/
20+
router.post('/', async (req: AuthenticatedRequest, res: Response) => {
21+
try {
22+
const { title = 'Untitled Document', folder_id = null } = req.body;
23+
const userId = req.user?.id;
24+
25+
if (!userId) {
26+
return res.status(401).json({ error: 'User not authenticated' });
27+
}
28+
29+
const result = await db.query(
30+
`INSERT INTO collaborative_documents
31+
(title, created_by, last_edited_by, document_subtype, folder_id, permissions, is_public)
32+
VALUES ($1, $2, $2, 'docs', $3, $4, false)
33+
RETURNING *`,
34+
[
35+
title,
36+
userId,
37+
folder_id,
38+
JSON.stringify({ [userId]: { level: 'owner', granted_at: new Date().toISOString() } })
39+
]
40+
);
41+
42+
return res.status(201).json(result[0]);
43+
} catch (error: any) {
44+
console.error('[Docs] Error creating document:', error);
45+
return res.status(500).json({ error: 'Failed to create document', details: error.message });
46+
}
47+
});
48+
49+
/**
50+
* @route GET /api/docs
51+
* @desc List all documents user has access to
52+
* @access Private
53+
*/
54+
router.get('/', async (req: AuthenticatedRequest, res: Response) => {
55+
try {
56+
const userId = req.user?.id;
57+
58+
if (!userId) {
59+
return res.status(401).json({ error: 'User not authenticated' });
60+
}
61+
62+
const result = await db.query(
63+
`SELECT
64+
cd.*,
65+
p.display_name as creator_name,
66+
le.display_name as last_editor_name
67+
FROM collaborative_documents cd
68+
LEFT JOIN profiles p ON cd.created_by = p.id
69+
LEFT JOIN profiles le ON cd.last_edited_by = le.id
70+
WHERE
71+
cd.document_subtype = 'docs'
72+
AND cd.is_deleted = false
73+
AND (
74+
cd.created_by = $1
75+
OR cd.permissions ? $1::text
76+
OR cd.is_public = true
77+
)
78+
ORDER BY cd.updated_at DESC`,
79+
[userId]
80+
);
81+
82+
return res.json(result);
83+
} catch (error: any) {
84+
console.error('[Docs] Error listing documents:', error);
85+
return res.status(500).json({ error: 'Failed to list documents', details: error.message });
86+
}
87+
});
88+
89+
/**
90+
* @route GET /api/docs/:id
91+
* @desc Get a specific document's metadata
92+
* @access Private
93+
*/
94+
router.get('/:id', async (req: AuthenticatedRequest, res: Response) => {
95+
try {
96+
const { id } = req.params;
97+
const userId = req.user?.id;
98+
99+
if (!userId) {
100+
return res.status(401).json({ error: 'User not authenticated' });
101+
}
102+
103+
const result = await db.query(
104+
`SELECT
105+
cd.*,
106+
p.display_name as creator_name,
107+
le.display_name as last_editor_name
108+
FROM collaborative_documents cd
109+
LEFT JOIN profiles p ON cd.created_by = p.id
110+
LEFT JOIN profiles le ON cd.last_edited_by = le.id
111+
WHERE
112+
cd.id = $1
113+
AND cd.document_subtype = 'docs'
114+
AND cd.is_deleted = false`,
115+
[id]
116+
);
117+
118+
if (result.length === 0) {
119+
return res.status(404).json({ error: 'Document not found' });
120+
}
121+
122+
const document = result[0];
123+
124+
const hasAccess =
125+
document.created_by === userId ||
126+
document.is_public ||
127+
(document.permissions && document.permissions[userId]);
128+
129+
if (!hasAccess) {
130+
return res.status(403).json({ error: 'Access denied' });
131+
}
132+
133+
return res.json(document);
134+
} catch (error: any) {
135+
console.error('[Docs] Error fetching document:', error);
136+
return res.status(500).json({ error: 'Failed to fetch document', details: error.message });
137+
}
138+
});
139+
140+
/**
141+
* @route PUT /api/docs/:id
142+
* @desc Update document metadata (title, folder)
143+
* @access Private
144+
*/
145+
router.put('/:id', async (req: AuthenticatedRequest, res: Response) => {
146+
try {
147+
const { id } = req.params;
148+
const { title, folder_id, content } = req.body;
149+
const userId = req.user?.id;
150+
151+
if (!userId) {
152+
return res.status(401).json({ error: 'User not authenticated' });
153+
}
154+
155+
const checkResult = await db.query(
156+
'SELECT permissions, created_by FROM collaborative_documents WHERE id = $1 AND document_subtype = $2 AND is_deleted = false',
157+
[id, 'docs']
158+
);
159+
160+
if (checkResult.length === 0) {
161+
return res.status(404).json({ error: 'Document not found' });
162+
}
163+
164+
const document = checkResult[0];
165+
const userPermission = document.permissions?.[userId];
166+
const isOwner = document.created_by === userId;
167+
const canEdit = isOwner || (userPermission && ['owner', 'editor'].includes(userPermission.level));
168+
169+
if (!canEdit) {
170+
return res.status(403).json({ error: 'Insufficient permissions to edit document' });
171+
}
172+
173+
const updates: string[] = [];
174+
const values: any[] = [];
175+
let paramIndex = 1;
176+
177+
if (title !== undefined) {
178+
updates.push(`title = $${paramIndex++}`);
179+
values.push(title);
180+
}
181+
182+
if (folder_id !== undefined) {
183+
updates.push(`folder_id = $${paramIndex++}`);
184+
values.push(folder_id);
185+
}
186+
187+
if (content !== undefined) {
188+
updates.push(`content = $${paramIndex++}`);
189+
values.push(content);
190+
}
191+
192+
updates.push(`last_edited_by = $${paramIndex++}`);
193+
values.push(userId);
194+
updates.push(`last_edited_at = CURRENT_TIMESTAMP`);
195+
196+
values.push(id);
197+
198+
const result = await db.query(
199+
`UPDATE collaborative_documents
200+
SET ${updates.join(', ')}, updated_at = CURRENT_TIMESTAMP
201+
WHERE id = $${paramIndex}
202+
RETURNING *`,
203+
values
204+
);
205+
206+
return res.json(result[0]);
207+
} catch (error: any) {
208+
console.error('[Docs] Error updating document:', error);
209+
return res.status(500).json({ error: 'Failed to update document', details: error.message });
210+
}
211+
});
212+
213+
/**
214+
* @route DELETE /api/docs/:id
215+
* @desc Soft delete a document
216+
* @access Private (Owner only)
217+
*/
218+
router.delete('/:id', async (req: AuthenticatedRequest, res: Response) => {
219+
try {
220+
const { id } = req.params;
221+
const userId = req.user?.id;
222+
223+
if (!userId) {
224+
return res.status(401).json({ error: 'User not authenticated' });
225+
}
226+
227+
const checkResult = await db.query(
228+
'SELECT created_by, permissions FROM collaborative_documents WHERE id = $1 AND document_subtype = $2 AND is_deleted = false',
229+
[id, 'docs']
230+
);
231+
232+
if (checkResult.length === 0) {
233+
return res.status(404).json({ error: 'Document not found' });
234+
}
235+
236+
const document = checkResult[0];
237+
const userPermission = document.permissions?.[userId];
238+
const isOwner = document.created_by === userId || (userPermission && userPermission.level === 'owner');
239+
240+
if (!isOwner) {
241+
return res.status(403).json({ error: 'Only owners can delete documents' });
242+
}
243+
244+
await db.query(
245+
'UPDATE collaborative_documents SET is_deleted = true, updated_at = CURRENT_TIMESTAMP WHERE id = $1',
246+
[id]
247+
);
248+
249+
return res.json({ message: 'Document deleted successfully' });
250+
} catch (error: any) {
251+
console.error('[Docs] Error deleting document:', error);
252+
return res.status(500).json({ error: 'Failed to delete document', details: error.message });
253+
}
254+
});
255+
256+
/**
257+
* @route POST /api/docs/:id/duplicate
258+
* @desc Duplicate a document
259+
* @access Private
260+
*/
261+
router.post('/:id/duplicate', async (req: AuthenticatedRequest, res: Response) => {
262+
try {
263+
const { id } = req.params;
264+
const userId = req.user?.id;
265+
266+
if (!userId) {
267+
return res.status(401).json({ error: 'User not authenticated' });
268+
}
269+
270+
const checkResult = await db.query(
271+
'SELECT * FROM collaborative_documents WHERE id = $1 AND document_subtype = $2 AND is_deleted = false',
272+
[id, 'docs']
273+
);
274+
275+
if (checkResult.length === 0) {
276+
return res.status(404).json({ error: 'Document not found' });
277+
}
278+
279+
const original = checkResult[0];
280+
281+
const hasAccess =
282+
original.created_by === userId ||
283+
original.is_public ||
284+
(original.permissions && original.permissions[userId]);
285+
286+
if (!hasAccess) {
287+
return res.status(403).json({ error: 'Access denied' });
288+
}
289+
290+
const newTitle = `${original.title} (Copy)`;
291+
const newDoc = await db.query(
292+
`INSERT INTO collaborative_documents
293+
(title, created_by, last_edited_by, document_subtype, permissions, is_public, content)
294+
VALUES ($1, $2, $2, 'docs', $3, false, $4)
295+
RETURNING *`,
296+
[
297+
newTitle,
298+
userId,
299+
JSON.stringify({ [userId]: { level: 'owner', granted_at: new Date().toISOString() } }),
300+
original.content || ''
301+
]
302+
);
303+
304+
return res.status(201).json(newDoc[0]);
305+
} catch (error: any) {
306+
console.error('[Docs] Error duplicating document:', error);
307+
return res.status(500).json({ error: 'Failed to duplicate document', details: error.message });
308+
}
309+
});
310+
311+
export default router;

0 commit comments

Comments
 (0)