studiograph 1.0.0
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/README.md +18 -0
- package/dist/agent/orchestrator.d.ts +69 -0
- package/dist/agent/orchestrator.js +211 -0
- package/dist/agent/orchestrator.js.map +1 -0
- package/dist/agent/tools/graph-tools.d.ts +30 -0
- package/dist/agent/tools/graph-tools.js +536 -0
- package/dist/agent/tools/graph-tools.js.map +1 -0
- package/dist/auth/github.d.ts +53 -0
- package/dist/auth/github.js +180 -0
- package/dist/auth/github.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +10 -0
- package/dist/cli/commands/auth.js +63 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/init.d.ts +7 -0
- package/dist/cli/commands/init.js +299 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/join.d.ts +14 -0
- package/dist/cli/commands/join.js +230 -0
- package/dist/cli/commands/join.js.map +1 -0
- package/dist/cli/commands/members.d.ts +11 -0
- package/dist/cli/commands/members.js +230 -0
- package/dist/cli/commands/members.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +17 -0
- package/dist/cli/commands/serve.js +90 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/start.d.ts +7 -0
- package/dist/cli/commands/start.js +381 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +10 -0
- package/dist/cli/commands/sync.js +121 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +31 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/graph.d.ts +169 -0
- package/dist/core/graph.js +558 -0
- package/dist/core/graph.js.map +1 -0
- package/dist/core/types.d.ts +216 -0
- package/dist/core/types.js +71 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/user-config.d.ts +31 -0
- package/dist/core/user-config.js +50 -0
- package/dist/core/user-config.js.map +1 -0
- package/dist/core/validation.d.ts +2371 -0
- package/dist/core/validation.js +432 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/core/workspace-manager.d.ts +104 -0
- package/dist/core/workspace-manager.js +432 -0
- package/dist/core/workspace-manager.js.map +1 -0
- package/dist/core/workspace.d.ts +103 -0
- package/dist/core/workspace.js +306 -0
- package/dist/core/workspace.js.map +1 -0
- package/dist/server/index.d.ts +25 -0
- package/dist/server/index.js +84 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/plugin-loader.d.ts +31 -0
- package/dist/server/plugin-loader.js +81 -0
- package/dist/server/plugin-loader.js.map +1 -0
- package/dist/server/routes/chat.d.ts +11 -0
- package/dist/server/routes/chat.js +66 -0
- package/dist/server/routes/chat.js.map +1 -0
- package/dist/server/routes/graph-api.d.ts +9 -0
- package/dist/server/routes/graph-api.js +72 -0
- package/dist/server/routes/graph-api.js.map +1 -0
- package/dist/server/routes/webhook.d.ts +14 -0
- package/dist/server/routes/webhook.js +69 -0
- package/dist/server/routes/webhook.js.map +1 -0
- package/dist/services/assets/base.d.ts +69 -0
- package/dist/services/assets/base.js +113 -0
- package/dist/services/assets/base.js.map +1 -0
- package/dist/services/assets/index.d.ts +36 -0
- package/dist/services/assets/index.js +89 -0
- package/dist/services/assets/index.js.map +1 -0
- package/dist/services/assets/local.d.ts +42 -0
- package/dist/services/assets/local.js +161 -0
- package/dist/services/assets/local.js.map +1 -0
- package/dist/services/assets/r2.d.ts +36 -0
- package/dist/services/assets/r2.js +182 -0
- package/dist/services/assets/r2.js.map +1 -0
- package/dist/services/csv-service.d.ts +36 -0
- package/dist/services/csv-service.js +143 -0
- package/dist/services/csv-service.js.map +1 -0
- package/dist/services/git.d.ts +99 -0
- package/dist/services/git.js +306 -0
- package/dist/services/git.js.map +1 -0
- package/dist/services/github-provisioner.d.ts +30 -0
- package/dist/services/github-provisioner.js +89 -0
- package/dist/services/github-provisioner.js.map +1 -0
- package/dist/services/markdown.d.ts +82 -0
- package/dist/services/markdown.js +338 -0
- package/dist/services/markdown.js.map +1 -0
- package/dist/services/memory-service.d.ts +74 -0
- package/dist/services/memory-service.js +183 -0
- package/dist/services/memory-service.js.map +1 -0
- package/dist/utils/git.d.ts +28 -0
- package/dist/utils/git.js +55 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/preflight.d.ts +44 -0
- package/dist/utils/preflight.js +95 -0
- package/dist/utils/preflight.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph API routes
|
|
3
|
+
*
|
|
4
|
+
* Read-only REST façade over WorkspaceManager.
|
|
5
|
+
* All writes go through the agent (/api/chat).
|
|
6
|
+
*/
|
|
7
|
+
export async function registerGraphApiRoutes(fastify, workspaceManager) {
|
|
8
|
+
// GET /api/repos — list all accessible repos
|
|
9
|
+
fastify.get('/api/repos', async (_req, reply) => {
|
|
10
|
+
const repos = workspaceManager.getAllRepoConfigs();
|
|
11
|
+
return reply.send(repos);
|
|
12
|
+
});
|
|
13
|
+
// GET /api/repos/:repo/entities/:type — list entities of a type
|
|
14
|
+
fastify.get('/api/repos/:repo/entities/:type', async (req, reply) => {
|
|
15
|
+
const { repo, type } = req.params;
|
|
16
|
+
let graph;
|
|
17
|
+
try {
|
|
18
|
+
graph = workspaceManager.getGraph(repo);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
return reply.status(404).send({ error: err.message });
|
|
22
|
+
}
|
|
23
|
+
const entities = graph.search({ entityType: type });
|
|
24
|
+
return reply.send(entities);
|
|
25
|
+
});
|
|
26
|
+
// GET /api/repos/:repo/entities/:type/:id — get single entity
|
|
27
|
+
fastify.get('/api/repos/:repo/entities/:type/:id', async (req, reply) => {
|
|
28
|
+
const { repo, type, id } = req.params;
|
|
29
|
+
let graph;
|
|
30
|
+
try {
|
|
31
|
+
graph = workspaceManager.getGraph(repo);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
return reply.status(404).send({ error: err.message });
|
|
35
|
+
}
|
|
36
|
+
const entity = graph.get(type, id);
|
|
37
|
+
if (!entity) {
|
|
38
|
+
return reply.status(404).send({ error: `Entity not found: ${type}/${id}` });
|
|
39
|
+
}
|
|
40
|
+
return reply.send(entity);
|
|
41
|
+
});
|
|
42
|
+
// GET /api/repos/:repo/search?q=&type=&tags= — search within a repo
|
|
43
|
+
fastify.get('/api/repos/:repo/search', async (req, reply) => {
|
|
44
|
+
const { repo } = req.params;
|
|
45
|
+
const { q, type, tags } = req.query;
|
|
46
|
+
let graph;
|
|
47
|
+
try {
|
|
48
|
+
graph = workspaceManager.getGraph(repo);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
return reply.status(404).send({ error: err.message });
|
|
52
|
+
}
|
|
53
|
+
const results = graph.search({
|
|
54
|
+
query: q,
|
|
55
|
+
entityType: type,
|
|
56
|
+
tags: tags ? tags.split(',').map(t => t.trim()) : undefined,
|
|
57
|
+
});
|
|
58
|
+
return reply.send(results);
|
|
59
|
+
});
|
|
60
|
+
// GET /api/search?q=&type=&tags=&repos= — cross-repo search
|
|
61
|
+
fastify.get('/api/search', async (req, reply) => {
|
|
62
|
+
const { q, type, tags, repos } = req.query;
|
|
63
|
+
const results = workspaceManager.search({
|
|
64
|
+
query: q,
|
|
65
|
+
entityType: type,
|
|
66
|
+
tags: tags ? tags.split(',').map(t => t.trim()) : undefined,
|
|
67
|
+
repoNames: repos ? repos.split(',').map(r => r.trim()) : undefined,
|
|
68
|
+
});
|
|
69
|
+
return reply.send(results);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=graph-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-api.js","sourceRoot":"","sources":["../../../src/server/routes/graph-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub webhook route
|
|
3
|
+
*
|
|
4
|
+
* For Config 2 deployments (Railway + GitHub).
|
|
5
|
+
* Validates HMAC-SHA256 signature, identifies the pushed repo,
|
|
6
|
+
* runs `git pull` on the matching local clone.
|
|
7
|
+
*
|
|
8
|
+
* Setup: add a webhook in GitHub repo settings pointing to
|
|
9
|
+
* POST https://<your-host>/webhooks/github
|
|
10
|
+
* with secret matching WEBHOOK_SECRET env var.
|
|
11
|
+
*/
|
|
12
|
+
import type { FastifyInstance } from 'fastify';
|
|
13
|
+
import { WorkspaceManager } from '../../core/workspace-manager.js';
|
|
14
|
+
export declare function registerWebhookRoutes(fastify: FastifyInstance, workspaceManager: WorkspaceManager, webhookSecret?: string): Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub webhook route
|
|
3
|
+
*
|
|
4
|
+
* For Config 2 deployments (Railway + GitHub).
|
|
5
|
+
* Validates HMAC-SHA256 signature, identifies the pushed repo,
|
|
6
|
+
* runs `git pull` on the matching local clone.
|
|
7
|
+
*
|
|
8
|
+
* Setup: add a webhook in GitHub repo settings pointing to
|
|
9
|
+
* POST https://<your-host>/webhooks/github
|
|
10
|
+
* with secret matching WEBHOOK_SECRET env var.
|
|
11
|
+
*/
|
|
12
|
+
import { createHmac, timingSafeEqual } from 'crypto';
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
export async function registerWebhookRoutes(fastify, workspaceManager, webhookSecret) {
|
|
15
|
+
fastify.post('/webhooks/github', { config: { rawBody: true } }, async (req, reply) => {
|
|
16
|
+
// Validate signature if secret is configured
|
|
17
|
+
if (webhookSecret) {
|
|
18
|
+
const signature = req.headers['x-hub-signature-256'];
|
|
19
|
+
if (!signature) {
|
|
20
|
+
return reply.status(401).send({ error: 'Missing X-Hub-Signature-256 header' });
|
|
21
|
+
}
|
|
22
|
+
const rawBody = req.rawBody ?? Buffer.from(JSON.stringify(req.body));
|
|
23
|
+
const expected = `sha256=${createHmac('sha256', webhookSecret).update(rawBody).digest('hex')}`;
|
|
24
|
+
let valid = false;
|
|
25
|
+
try {
|
|
26
|
+
valid = timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
valid = false;
|
|
30
|
+
}
|
|
31
|
+
if (!valid) {
|
|
32
|
+
return reply.status(401).send({ error: 'Invalid webhook signature' });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const event = req.headers['x-github-event'];
|
|
36
|
+
// Only process push events
|
|
37
|
+
if (event !== 'push') {
|
|
38
|
+
return reply.send({ ok: true, skipped: true, reason: `event ${event} ignored` });
|
|
39
|
+
}
|
|
40
|
+
const payload = req.body;
|
|
41
|
+
const repoName = payload?.repository?.name;
|
|
42
|
+
if (!repoName) {
|
|
43
|
+
return reply.status(400).send({ error: 'Cannot determine repository name from payload' });
|
|
44
|
+
}
|
|
45
|
+
// Find matching repo config
|
|
46
|
+
const repoConfig = workspaceManager.getRepoConfig(repoName);
|
|
47
|
+
if (!repoConfig) {
|
|
48
|
+
fastify.log.warn(`Webhook: no repo config found for "${repoName}"`);
|
|
49
|
+
return reply.send({ ok: true, skipped: true, reason: `repo ${repoName} not in workspace` });
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
// Get the graph's underlying repo path
|
|
53
|
+
const graph = workspaceManager.getGraph(repoName);
|
|
54
|
+
const repoPath = graph.repoPath;
|
|
55
|
+
execSync('git pull --ff-only', {
|
|
56
|
+
cwd: repoPath,
|
|
57
|
+
stdio: 'pipe',
|
|
58
|
+
timeout: 30_000,
|
|
59
|
+
});
|
|
60
|
+
fastify.log.info(`Webhook: git pull completed for repo "${repoName}"`);
|
|
61
|
+
return reply.send({ ok: true, repo: repoName });
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
fastify.log.error(err, `Webhook: git pull failed for repo "${repoName}"`);
|
|
65
|
+
return reply.status(500).send({ error: `git pull failed: ${err.message}` });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../../src/server/routes/webhook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base types and interfaces for asset management
|
|
3
|
+
*/
|
|
4
|
+
export interface AssetInfo {
|
|
5
|
+
filename: string;
|
|
6
|
+
size: number;
|
|
7
|
+
contentType: string;
|
|
8
|
+
url: string;
|
|
9
|
+
path: string;
|
|
10
|
+
mediaType: 'image' | 'video' | 'audio' | 'document' | 'other';
|
|
11
|
+
}
|
|
12
|
+
export interface AssetBackend {
|
|
13
|
+
/**
|
|
14
|
+
* Upload asset to storage
|
|
15
|
+
*/
|
|
16
|
+
upload(key: string, buffer: Buffer, contentType: string): Promise<AssetInfo>;
|
|
17
|
+
/**
|
|
18
|
+
* List assets with a given prefix
|
|
19
|
+
*/
|
|
20
|
+
list(prefix: string): Promise<AssetInfo[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Delete a single asset
|
|
23
|
+
*/
|
|
24
|
+
delete(key: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Delete all assets with a given prefix
|
|
27
|
+
*/
|
|
28
|
+
deleteAll(prefix: string): Promise<number>;
|
|
29
|
+
/**
|
|
30
|
+
* Check if an asset exists
|
|
31
|
+
*/
|
|
32
|
+
exists(key: string): Promise<boolean>;
|
|
33
|
+
}
|
|
34
|
+
export interface AssetConfig {
|
|
35
|
+
storage: 'local' | 'r2';
|
|
36
|
+
localPath?: string;
|
|
37
|
+
r2Config?: R2Config;
|
|
38
|
+
}
|
|
39
|
+
export interface R2Config {
|
|
40
|
+
accountId: string;
|
|
41
|
+
bucket: string;
|
|
42
|
+
publicUrl: string;
|
|
43
|
+
accessKeyId: string;
|
|
44
|
+
secretAccessKey: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Content type mappings
|
|
48
|
+
*/
|
|
49
|
+
export declare const CONTENT_TYPES: Record<string, string>;
|
|
50
|
+
/**
|
|
51
|
+
* Detect content type from filename
|
|
52
|
+
*/
|
|
53
|
+
export declare function detectContentType(filename: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Determine media type from content type
|
|
56
|
+
*/
|
|
57
|
+
export declare function getMediaType(contentType: string): AssetInfo['mediaType'];
|
|
58
|
+
/**
|
|
59
|
+
* Validate filename for security
|
|
60
|
+
*/
|
|
61
|
+
export declare function validateFilename(filename: string): void;
|
|
62
|
+
/**
|
|
63
|
+
* Build asset key from components
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildAssetKey(repo: string, entityType: string, entityId: string, filename: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Build asset prefix for listing
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildAssetPrefix(repo: string, entityType: string, entityId: string): string;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base types and interfaces for asset management
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Content type mappings
|
|
6
|
+
*/
|
|
7
|
+
export const CONTENT_TYPES = {
|
|
8
|
+
// Images
|
|
9
|
+
png: 'image/png',
|
|
10
|
+
jpg: 'image/jpeg',
|
|
11
|
+
jpeg: 'image/jpeg',
|
|
12
|
+
gif: 'image/gif',
|
|
13
|
+
webp: 'image/webp',
|
|
14
|
+
svg: 'image/svg+xml',
|
|
15
|
+
ico: 'image/x-icon',
|
|
16
|
+
// Videos
|
|
17
|
+
mp4: 'video/mp4',
|
|
18
|
+
mov: 'video/quicktime',
|
|
19
|
+
webm: 'video/webm',
|
|
20
|
+
avi: 'video/x-msvideo',
|
|
21
|
+
// Audio
|
|
22
|
+
mp3: 'audio/mpeg',
|
|
23
|
+
wav: 'audio/wav',
|
|
24
|
+
ogg: 'audio/ogg',
|
|
25
|
+
m4a: 'audio/mp4',
|
|
26
|
+
// Documents
|
|
27
|
+
pdf: 'application/pdf',
|
|
28
|
+
doc: 'application/msword',
|
|
29
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
30
|
+
xls: 'application/vnd.ms-excel',
|
|
31
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
32
|
+
ppt: 'application/vnd.ms-powerpoint',
|
|
33
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
34
|
+
// Archives
|
|
35
|
+
zip: 'application/zip',
|
|
36
|
+
tar: 'application/x-tar',
|
|
37
|
+
gz: 'application/gzip',
|
|
38
|
+
// Design files
|
|
39
|
+
fig: 'application/octet-stream',
|
|
40
|
+
sketch: 'application/octet-stream',
|
|
41
|
+
// Text
|
|
42
|
+
txt: 'text/plain',
|
|
43
|
+
json: 'application/json',
|
|
44
|
+
xml: 'application/xml',
|
|
45
|
+
csv: 'text/csv',
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Detect content type from filename
|
|
49
|
+
*/
|
|
50
|
+
export function detectContentType(filename) {
|
|
51
|
+
const ext = filename.split('.').pop()?.toLowerCase() || '';
|
|
52
|
+
return CONTENT_TYPES[ext] || 'application/octet-stream';
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Determine media type from content type
|
|
56
|
+
*/
|
|
57
|
+
export function getMediaType(contentType) {
|
|
58
|
+
if (contentType.startsWith('image/'))
|
|
59
|
+
return 'image';
|
|
60
|
+
if (contentType.startsWith('video/'))
|
|
61
|
+
return 'video';
|
|
62
|
+
if (contentType.startsWith('audio/'))
|
|
63
|
+
return 'audio';
|
|
64
|
+
if (contentType.includes('pdf') ||
|
|
65
|
+
contentType.includes('document') ||
|
|
66
|
+
contentType.includes('word') ||
|
|
67
|
+
contentType.includes('excel') ||
|
|
68
|
+
contentType.includes('powerpoint') ||
|
|
69
|
+
contentType.includes('presentation')) {
|
|
70
|
+
return 'document';
|
|
71
|
+
}
|
|
72
|
+
return 'other';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Validate filename for security
|
|
76
|
+
*/
|
|
77
|
+
export function validateFilename(filename) {
|
|
78
|
+
if (!filename || filename.trim() === '') {
|
|
79
|
+
throw new Error('Filename cannot be empty');
|
|
80
|
+
}
|
|
81
|
+
// Check for path traversal
|
|
82
|
+
if (filename.includes('..') ||
|
|
83
|
+
filename.includes('/') ||
|
|
84
|
+
filename.includes('\\')) {
|
|
85
|
+
throw new Error('Filename cannot contain path separators or traversal sequences');
|
|
86
|
+
}
|
|
87
|
+
// Check for hidden files
|
|
88
|
+
if (filename.startsWith('.')) {
|
|
89
|
+
throw new Error('Filename cannot start with a dot (hidden files not allowed)');
|
|
90
|
+
}
|
|
91
|
+
// Check for forbidden characters
|
|
92
|
+
const forbiddenChars = /[<>:"|?*\x00-\x1f]/;
|
|
93
|
+
if (forbiddenChars.test(filename)) {
|
|
94
|
+
throw new Error('Filename contains forbidden characters');
|
|
95
|
+
}
|
|
96
|
+
// Check length
|
|
97
|
+
if (filename.length > 255) {
|
|
98
|
+
throw new Error('Filename too long (max 255 characters)');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Build asset key from components
|
|
103
|
+
*/
|
|
104
|
+
export function buildAssetKey(repo, entityType, entityId, filename) {
|
|
105
|
+
return `${repo}/${entityType}/${entityId}/${filename}`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Build asset prefix for listing
|
|
109
|
+
*/
|
|
110
|
+
export function buildAssetPrefix(repo, entityType, entityId) {
|
|
111
|
+
return `${repo}/${entityType}/${entityId}/`;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/services/assets/base.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset service for Studiograph
|
|
3
|
+
*
|
|
4
|
+
* Manages all media assets (images, videos, audio, documents)
|
|
5
|
+
* with pluggable storage backends (local filesystem or R2)
|
|
6
|
+
*/
|
|
7
|
+
import type { AssetInfo, AssetConfig } from './base.js';
|
|
8
|
+
export * from './base.js';
|
|
9
|
+
export declare class AssetService {
|
|
10
|
+
private backend;
|
|
11
|
+
constructor(config: AssetConfig);
|
|
12
|
+
/**
|
|
13
|
+
* Upload asset for an entity
|
|
14
|
+
*/
|
|
15
|
+
upload(repo: string, entityType: string, entityId: string, filename: string, buffer: Buffer): Promise<AssetInfo>;
|
|
16
|
+
/**
|
|
17
|
+
* List all assets for an entity
|
|
18
|
+
*/
|
|
19
|
+
list(repo: string, entityType: string, entityId: string): Promise<AssetInfo[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Delete a single asset
|
|
22
|
+
*/
|
|
23
|
+
delete(repo: string, entityType: string, entityId: string, filename: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Delete all assets for an entity (when entity is deleted)
|
|
26
|
+
*/
|
|
27
|
+
deleteAll(repo: string, entityType: string, entityId: string): Promise<number>;
|
|
28
|
+
/**
|
|
29
|
+
* Check if an asset exists
|
|
30
|
+
*/
|
|
31
|
+
exists(repo: string, entityType: string, entityId: string, filename: string): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Get asset info without downloading
|
|
34
|
+
*/
|
|
35
|
+
getInfo(repo: string, entityType: string, entityId: string, filename: string): Promise<AssetInfo | null>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset service for Studiograph
|
|
3
|
+
*
|
|
4
|
+
* Manages all media assets (images, videos, audio, documents)
|
|
5
|
+
* with pluggable storage backends (local filesystem or R2)
|
|
6
|
+
*/
|
|
7
|
+
import { validateFilename, detectContentType, buildAssetKey, buildAssetPrefix, } from './base.js';
|
|
8
|
+
import { LocalAssetBackend } from './local.js';
|
|
9
|
+
import { R2AssetBackend } from './r2.js';
|
|
10
|
+
export * from './base.js';
|
|
11
|
+
export class AssetService {
|
|
12
|
+
backend;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
// Initialize appropriate backend
|
|
15
|
+
if (config.storage === 'local') {
|
|
16
|
+
const localPath = config.localPath || '.studiograph/assets';
|
|
17
|
+
this.backend = new LocalAssetBackend(localPath);
|
|
18
|
+
}
|
|
19
|
+
else if (config.storage === 'r2') {
|
|
20
|
+
if (!config.r2Config) {
|
|
21
|
+
throw new Error('R2 config is required when storage is set to r2');
|
|
22
|
+
}
|
|
23
|
+
this.backend = new R2AssetBackend(config.r2Config);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
throw new Error(`Unknown storage backend: ${config.storage}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Upload asset for an entity
|
|
31
|
+
*/
|
|
32
|
+
async upload(repo, entityType, entityId, filename, buffer) {
|
|
33
|
+
// Validate filename
|
|
34
|
+
validateFilename(filename);
|
|
35
|
+
// Validate buffer
|
|
36
|
+
if (!buffer || buffer.length === 0) {
|
|
37
|
+
throw new Error('Buffer cannot be empty');
|
|
38
|
+
}
|
|
39
|
+
// Check file size (max 100MB)
|
|
40
|
+
const maxSize = 100 * 1024 * 1024; // 100MB
|
|
41
|
+
if (buffer.length > maxSize) {
|
|
42
|
+
throw new Error(`File too large (max ${maxSize / 1024 / 1024}MB)`);
|
|
43
|
+
}
|
|
44
|
+
// Detect content type
|
|
45
|
+
const contentType = detectContentType(filename);
|
|
46
|
+
// Build storage key
|
|
47
|
+
const key = buildAssetKey(repo, entityType, entityId, filename);
|
|
48
|
+
// Upload via backend
|
|
49
|
+
return await this.backend.upload(key, buffer, contentType);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* List all assets for an entity
|
|
53
|
+
*/
|
|
54
|
+
async list(repo, entityType, entityId) {
|
|
55
|
+
const prefix = buildAssetPrefix(repo, entityType, entityId);
|
|
56
|
+
return await this.backend.list(prefix);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Delete a single asset
|
|
60
|
+
*/
|
|
61
|
+
async delete(repo, entityType, entityId, filename) {
|
|
62
|
+
validateFilename(filename);
|
|
63
|
+
const key = buildAssetKey(repo, entityType, entityId, filename);
|
|
64
|
+
return await this.backend.delete(key);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Delete all assets for an entity (when entity is deleted)
|
|
68
|
+
*/
|
|
69
|
+
async deleteAll(repo, entityType, entityId) {
|
|
70
|
+
const prefix = buildAssetPrefix(repo, entityType, entityId);
|
|
71
|
+
return await this.backend.deleteAll(prefix);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if an asset exists
|
|
75
|
+
*/
|
|
76
|
+
async exists(repo, entityType, entityId, filename) {
|
|
77
|
+
validateFilename(filename);
|
|
78
|
+
const key = buildAssetKey(repo, entityType, entityId, filename);
|
|
79
|
+
return await this.backend.exists(key);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get asset info without downloading
|
|
83
|
+
*/
|
|
84
|
+
async getInfo(repo, entityType, entityId, filename) {
|
|
85
|
+
const assets = await this.list(repo, entityType, entityId);
|
|
86
|
+
return assets.find(a => a.filename === filename) || null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/assets/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local filesystem asset backend
|
|
3
|
+
*
|
|
4
|
+
* Stores assets in .studiograph/assets/ directory
|
|
5
|
+
*/
|
|
6
|
+
import type { AssetBackend, AssetInfo } from './base.js';
|
|
7
|
+
export declare class LocalAssetBackend implements AssetBackend {
|
|
8
|
+
private basePath;
|
|
9
|
+
constructor(basePath: string);
|
|
10
|
+
/**
|
|
11
|
+
* Ensure base directory exists
|
|
12
|
+
*/
|
|
13
|
+
private ensureBaseDirectory;
|
|
14
|
+
/**
|
|
15
|
+
* Get full file path for a key
|
|
16
|
+
*/
|
|
17
|
+
private getFilePath;
|
|
18
|
+
/**
|
|
19
|
+
* Upload asset to local filesystem
|
|
20
|
+
*/
|
|
21
|
+
upload(key: string, buffer: Buffer, contentType: string): Promise<AssetInfo>;
|
|
22
|
+
/**
|
|
23
|
+
* List assets with a given prefix
|
|
24
|
+
*/
|
|
25
|
+
list(prefix: string): Promise<AssetInfo[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Delete a single asset
|
|
28
|
+
*/
|
|
29
|
+
delete(key: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Delete all assets with a given prefix
|
|
32
|
+
*/
|
|
33
|
+
deleteAll(prefix: string): Promise<number>;
|
|
34
|
+
/**
|
|
35
|
+
* Check if an asset exists
|
|
36
|
+
*/
|
|
37
|
+
exists(key: string): Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Guess content type from file extension
|
|
40
|
+
*/
|
|
41
|
+
private guessContentType;
|
|
42
|
+
}
|