aboutsummaryrefslogtreecommitdiff
path: root/src/storage/index.ts
blob: 2c8ffb2b87bcd05700c02ba09714da62c489bab6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { randomUUID } from 'crypto';
import { mkdir, writeFile, readFile } from 'fs/promises';
import { join } from 'path';
import { isSafeRouteName, type RouteConfig, type StoredRequest } from '../types/index.js';
import { Either, type IEither } from '@emprespresso/pengueno';

export class Storage {
    private routes: Map<string, RouteConfig> = new Map();

    constructor(private readonly dataDir: string = './data') {}

    async init(): Promise<IEither<Error, void>> {
        try {
            await mkdir(this.dataDir, { recursive: true });
            await this.loadRoutes();
            return Either.right(<void>undefined);
        } catch (err) {
            return Either.left(err instanceof Error ? err : new Error(String(err)));
        }
    }

    private async loadRoutes(): Promise<void> {
        try {
            const routesPath = join(this.dataDir, 'routes.json');
            const data = await readFile(routesPath, 'utf-8');
            const routes = JSON.parse(data) as RouteConfig[];
            for (const route of routes) {
                if (!isSafeRouteName(route.name)) {
                    continue;
                }
                this.routes.set(route.name, route);
            }
        } catch {
            // routes file doesn't exist yet, that's ok
        }
    }

    private async saveRoutes(): Promise<IEither<Error, void>> {
        try {
            const routesPath = join(this.dataDir, 'routes.json');
            const routes = Array.from(this.routes.values());
            await writeFile(routesPath, JSON.stringify(routes, null, 2));
            return Either.right(<void>undefined);
        } catch (err) {
            return Either.left(err instanceof Error ? err : new Error(String(err)));
        }
    }

    async registerRoute(config: RouteConfig): Promise<IEither<Error, void>> {
        if (!isSafeRouteName(config.name)) {
            return Either.left(new Error('Invalid route name'));
        }

        this.routes.set(config.name, config);
        const routeDir = join(this.dataDir, config.name);
        try {
            await mkdir(routeDir, { recursive: true });
        } catch (err) {
            return Either.left(err instanceof Error ? err : new Error(String(err)));
        }
        return this.saveRoutes();
    }

    getRoute(name: string): RouteConfig | undefined {
        if (!isSafeRouteName(name)) return undefined;
        return this.routes.get(name);
    }

    listRoutes(): RouteConfig[] {
        return Array.from(this.routes.values());
    }

    async deleteRoute(name: string): Promise<IEither<Error, void>> {
        if (!isSafeRouteName(name)) {
            return Either.left(new Error('Invalid route name'));
        }
        this.routes.delete(name);
        return this.saveRoutes();
    }

    async storeRequest(
        routeName: string,
        method: string,
        headers: Record<string, string>,
        body: unknown,
        files?: StoredRequest['files'],
    ): Promise<IEither<Error, StoredRequest>> {
        if (!isSafeRouteName(routeName)) {
            return Either.left(new Error('Invalid route name'));
        }

        const timestamp = Date.now();
        const uuid = randomUUID();
        const filename = `${timestamp}_${uuid}.json`;

        const stored: StoredRequest = {
            timestamp,
            uuid,
            routeName,
            method,
            headers,
            body,
            files,
        };

        const filepath = join(this.dataDir, routeName, filename);
        try {
            await writeFile(filepath, JSON.stringify(stored, null, 2));
            return Either.right(stored);
        } catch (err) {
            return Either.left(err instanceof Error ? err : new Error(String(err)));
        }
    }
}