@theihtisham/devtools-with-cloud 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/.env.example +15 -0
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/docker-compose.yml +23 -0
- package/jest.config.js +7 -0
- package/next-env.d.ts +5 -0
- package/next.config.mjs +22 -0
- package/package.json +82 -0
- package/postcss.config.js +6 -0
- package/prisma/schema.prisma +105 -0
- package/prisma/seed.ts +211 -0
- package/src/app/(app)/ai/page.tsx +122 -0
- package/src/app/(app)/collections/page.tsx +155 -0
- package/src/app/(app)/environments/page.tsx +96 -0
- package/src/app/(app)/history/page.tsx +107 -0
- package/src/app/(app)/import/page.tsx +102 -0
- package/src/app/(app)/layout.tsx +60 -0
- package/src/app/(app)/settings/page.tsx +79 -0
- package/src/app/(app)/workspace/page.tsx +284 -0
- package/src/app/api/ai/discover/route.ts +17 -0
- package/src/app/api/ai/explain/route.ts +29 -0
- package/src/app/api/ai/generate-tests/route.ts +37 -0
- package/src/app/api/ai/suggest/route.ts +29 -0
- package/src/app/api/collections/[id]/route.ts +66 -0
- package/src/app/api/collections/route.ts +48 -0
- package/src/app/api/environments/route.ts +40 -0
- package/src/app/api/export/openapi/route.ts +17 -0
- package/src/app/api/export/postman/route.ts +18 -0
- package/src/app/api/import/curl/route.ts +18 -0
- package/src/app/api/import/har/route.ts +20 -0
- package/src/app/api/import/openapi/route.ts +21 -0
- package/src/app/api/import/postman/route.ts +21 -0
- package/src/app/api/proxy/route.ts +35 -0
- package/src/app/api/requests/[id]/execute/route.ts +85 -0
- package/src/app/api/requests/[id]/history/route.ts +23 -0
- package/src/app/api/requests/[id]/route.ts +66 -0
- package/src/app/api/requests/route.ts +49 -0
- package/src/app/api/workspaces/route.ts +38 -0
- package/src/app/globals.css +99 -0
- package/src/app/layout.tsx +24 -0
- package/src/app/page.tsx +182 -0
- package/src/components/ai/ai-panel.tsx +65 -0
- package/src/components/ai/code-explainer.tsx +51 -0
- package/src/components/ai/endpoint-discovery.tsx +62 -0
- package/src/components/ai/test-generator.tsx +49 -0
- package/src/components/collections/collection-actions.tsx +36 -0
- package/src/components/collections/collection-tree.tsx +55 -0
- package/src/components/collections/folder-creator.tsx +54 -0
- package/src/components/landing/comparison.tsx +43 -0
- package/src/components/landing/cta.tsx +16 -0
- package/src/components/landing/features.tsx +24 -0
- package/src/components/landing/hero.tsx +23 -0
- package/src/components/response/body-viewer.tsx +33 -0
- package/src/components/response/headers-viewer.tsx +23 -0
- package/src/components/response/status-badge.tsx +25 -0
- package/src/components/response/test-results.tsx +50 -0
- package/src/components/response/timing-chart.tsx +39 -0
- package/src/components/ui/badge.tsx +24 -0
- package/src/components/ui/button.tsx +32 -0
- package/src/components/ui/code-editor.tsx +51 -0
- package/src/components/ui/dialog.tsx +56 -0
- package/src/components/ui/dropdown.tsx +63 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/key-value-editor.tsx +75 -0
- package/src/components/ui/select.tsx +24 -0
- package/src/components/ui/tabs.tsx +85 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/toast.tsx +54 -0
- package/src/components/workspace/request-panel.tsx +38 -0
- package/src/components/workspace/response-panel.tsx +81 -0
- package/src/components/workspace/sidebar.tsx +52 -0
- package/src/components/workspace/split-pane.tsx +49 -0
- package/src/components/workspace/tabs/auth-tab.tsx +94 -0
- package/src/components/workspace/tabs/body-tab.tsx +41 -0
- package/src/components/workspace/tabs/headers-tab.tsx +23 -0
- package/src/components/workspace/tabs/params-tab.tsx +23 -0
- package/src/components/workspace/tabs/pre-request-tab.tsx +26 -0
- package/src/components/workspace/url-bar.tsx +53 -0
- package/src/hooks/use-ai.ts +115 -0
- package/src/hooks/use-collection.ts +71 -0
- package/src/hooks/use-environment.ts +73 -0
- package/src/hooks/use-request.ts +111 -0
- package/src/lib/ai/endpoint-discovery.ts +158 -0
- package/src/lib/ai/explainer.ts +127 -0
- package/src/lib/ai/suggester.ts +164 -0
- package/src/lib/ai/test-generator.ts +161 -0
- package/src/lib/auth/api-key.ts +28 -0
- package/src/lib/auth/aws-sig.ts +131 -0
- package/src/lib/auth/basic.ts +17 -0
- package/src/lib/auth/bearer.ts +15 -0
- package/src/lib/auth/oauth2.ts +155 -0
- package/src/lib/auth/types.ts +16 -0
- package/src/lib/db/client.ts +15 -0
- package/src/lib/env/manager.ts +32 -0
- package/src/lib/env/resolver.ts +30 -0
- package/src/lib/exporters/openapi.ts +193 -0
- package/src/lib/exporters/postman.ts +140 -0
- package/src/lib/graphql/builder.ts +249 -0
- package/src/lib/graphql/formatter.ts +147 -0
- package/src/lib/graphql/index.ts +43 -0
- package/src/lib/graphql/introspection.ts +175 -0
- package/src/lib/graphql/types.ts +99 -0
- package/src/lib/graphql/validator.ts +216 -0
- package/src/lib/http/client.ts +112 -0
- package/src/lib/http/proxy.ts +83 -0
- package/src/lib/http/request-builder.ts +214 -0
- package/src/lib/http/response-parser.ts +106 -0
- package/src/lib/http/timing.ts +63 -0
- package/src/lib/importers/curl-parser.ts +346 -0
- package/src/lib/importers/har-parser.ts +128 -0
- package/src/lib/importers/openapi.ts +324 -0
- package/src/lib/importers/postman.ts +312 -0
- package/src/lib/test-runner/assertions.ts +163 -0
- package/src/lib/test-runner/reporter.ts +90 -0
- package/src/lib/test-runner/runner.ts +69 -0
- package/src/lib/utils/api-response.ts +85 -0
- package/src/lib/utils/cn.ts +6 -0
- package/src/lib/utils/content-type.ts +123 -0
- package/src/lib/utils/download.ts +53 -0
- package/src/lib/utils/errors.ts +92 -0
- package/src/lib/utils/format.ts +142 -0
- package/src/lib/utils/syntax-highlight.ts +108 -0
- package/src/lib/utils/validation.ts +231 -0
- package/src/lib/websocket/client.ts +182 -0
- package/src/lib/websocket/frames.ts +96 -0
- package/src/lib/websocket/history.ts +121 -0
- package/src/lib/websocket/index.ts +25 -0
- package/src/lib/websocket/types.ts +57 -0
- package/src/types/ai.ts +28 -0
- package/src/types/collection.ts +24 -0
- package/src/types/environment.ts +16 -0
- package/src/types/request.ts +54 -0
- package/src/types/response.ts +37 -0
- package/tailwind.config.ts +82 -0
- package/tests/lib/env/resolver.test.ts +108 -0
- package/tests/lib/graphql/builder.test.ts +349 -0
- package/tests/lib/graphql/formatter.test.ts +99 -0
- package/tests/lib/http/request-builder.test.ts +160 -0
- package/tests/lib/http/response-parser.test.ts +150 -0
- package/tests/lib/http/timing.test.ts +188 -0
- package/tests/lib/importers/curl-parser.test.ts +245 -0
- package/tests/lib/test-runner/assertions.test.ts +342 -0
- package/tests/lib/utils/cn.test.ts +46 -0
- package/tests/lib/utils/content-type.test.ts +175 -0
- package/tests/lib/utils/format.test.ts +188 -0
- package/tests/lib/utils/validation.test.ts +237 -0
- package/tests/lib/websocket/history.test.ts +186 -0
- package/tsconfig.json +29 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +21 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { success, error } from '@/lib/utils/api-response';
|
|
3
|
+
import { ImportCurlSchema } from '@/lib/utils/validation';
|
|
4
|
+
import { parseCurl } from '@/lib/importers/curl-parser';
|
|
5
|
+
|
|
6
|
+
export async function POST(req: NextRequest): Promise<Response> {
|
|
7
|
+
try {
|
|
8
|
+
const body = await req.json();
|
|
9
|
+
const parsed = ImportCurlSchema.parse(body);
|
|
10
|
+
|
|
11
|
+
const request = parseCurl(parsed.command);
|
|
12
|
+
request.name = parsed.name ?? `${request.method} ${request.url}`;
|
|
13
|
+
|
|
14
|
+
return success({ request }) as Response;
|
|
15
|
+
} catch (err) {
|
|
16
|
+
return error(err) as Response;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { success, error } from '@/lib/utils/api-response';
|
|
3
|
+
import { ImportHarSchema } from '@/lib/utils/validation';
|
|
4
|
+
import { importHar } from '@/lib/importers/har-parser';
|
|
5
|
+
|
|
6
|
+
export async function POST(req: NextRequest): Promise<Response> {
|
|
7
|
+
try {
|
|
8
|
+
const body = await req.json();
|
|
9
|
+
const parsed = ImportHarSchema.parse(body);
|
|
10
|
+
|
|
11
|
+
const result = importHar(parsed.har);
|
|
12
|
+
|
|
13
|
+
return success({
|
|
14
|
+
entries: result.entries,
|
|
15
|
+
entryCount: result.entries.length,
|
|
16
|
+
}) as Response;
|
|
17
|
+
} catch (err) {
|
|
18
|
+
return error(err) as Response;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { success, error } from '@/lib/utils/api-response';
|
|
3
|
+
import { ImportOpenApiSchema } from '@/lib/utils/validation';
|
|
4
|
+
import { importOpenApi } from '@/lib/importers/openapi';
|
|
5
|
+
|
|
6
|
+
export async function POST(req: NextRequest): Promise<Response> {
|
|
7
|
+
try {
|
|
8
|
+
const body = await req.json();
|
|
9
|
+
const parsed = ImportOpenApiSchema.parse(body);
|
|
10
|
+
|
|
11
|
+
const collection = importOpenApi(parsed.spec);
|
|
12
|
+
collection.name = parsed.collectionName ?? collection.name;
|
|
13
|
+
|
|
14
|
+
return success({
|
|
15
|
+
collection,
|
|
16
|
+
endpointCount: collection.endpoints.length,
|
|
17
|
+
}) as Response;
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return error(err) as Response;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { success, error } from '@/lib/utils/api-response';
|
|
3
|
+
import { ImportPostmanSchema } from '@/lib/utils/validation';
|
|
4
|
+
import { importPostman } from '@/lib/importers/postman';
|
|
5
|
+
|
|
6
|
+
export async function POST(req: NextRequest): Promise<Response> {
|
|
7
|
+
try {
|
|
8
|
+
const body = await req.json();
|
|
9
|
+
const parsed = ImportPostmanSchema.parse(body);
|
|
10
|
+
|
|
11
|
+
const collection = importPostman(parsed.collection);
|
|
12
|
+
|
|
13
|
+
return success({
|
|
14
|
+
collection,
|
|
15
|
+
folderCount: collection.folders.length,
|
|
16
|
+
requestCount: collection.requests.length + collection.folders.reduce((sum, f) => sum + f.requests.length, 0),
|
|
17
|
+
}) as Response;
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return error(err) as Response;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { proxyRequest, handleCorsPreflight } from '@/lib/http/proxy';
|
|
3
|
+
import { success, error } from '@/lib/utils/api-response';
|
|
4
|
+
|
|
5
|
+
export async function OPTIONS(): Promise<NextResponse> {
|
|
6
|
+
return handleCorsPreflight();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function POST(req: NextRequest): Promise<NextResponse> {
|
|
10
|
+
try {
|
|
11
|
+
const body = await req.json() as {
|
|
12
|
+
url: string;
|
|
13
|
+
method: string;
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
body?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
if (!body.url) {
|
|
19
|
+
return NextResponse.json(
|
|
20
|
+
{ success: false, error: { message: 'URL is required', code: 'VALIDATION_ERROR' } },
|
|
21
|
+
{ status: 400 },
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return await proxyRequest(
|
|
26
|
+
req,
|
|
27
|
+
body.url,
|
|
28
|
+
body.method ?? 'GET',
|
|
29
|
+
body.headers ?? {},
|
|
30
|
+
body.body ?? null,
|
|
31
|
+
);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
return error(err);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { prisma } from '@/lib/db/client';
|
|
3
|
+
import { success, error } from '@/lib/utils/api-response';
|
|
4
|
+
import { NotFoundError } from '@/lib/utils/errors';
|
|
5
|
+
import { executeRequest } from '@/lib/http/client';
|
|
6
|
+
import { buildRequest } from '@/lib/http/request-builder';
|
|
7
|
+
import type { AuthConfig } from '@/lib/utils/validation';
|
|
8
|
+
|
|
9
|
+
interface RouteContext {
|
|
10
|
+
params: { id: string };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function POST(req: NextRequest, context: RouteContext): Promise<Response> {
|
|
14
|
+
try {
|
|
15
|
+
const { id } = context.params;
|
|
16
|
+
|
|
17
|
+
const request = await prisma.request.findUnique({
|
|
18
|
+
where: { id },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (!request) {
|
|
22
|
+
throw new NotFoundError('Request', id);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Get environment variables (use default environment)
|
|
26
|
+
const collection = await prisma.collection.findUnique({ where: { id: request.collectionId } });
|
|
27
|
+
const workspaceId = collection?.workspaceId;
|
|
28
|
+
const env = await prisma.environment.findFirst({
|
|
29
|
+
where: workspaceId ? { workspaceId, isDefault: true } : { isDefault: true },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const variables: Record<string, string> = {};
|
|
33
|
+
if (env && Array.isArray(env.variables)) {
|
|
34
|
+
for (const v of (env.variables as Array<{ key: string; value: string; enabled?: boolean }>)) {
|
|
35
|
+
if (v.enabled !== false) {
|
|
36
|
+
variables[v.key] = v.value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Build the request
|
|
42
|
+
const built = buildRequest(
|
|
43
|
+
request.method as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'TRACE' | 'CONNECT',
|
|
44
|
+
request.url,
|
|
45
|
+
request.headers as Array<{ key: string; value: string; enabled: boolean }>,
|
|
46
|
+
request.params as Array<{ key: string; value: string; enabled: boolean }>,
|
|
47
|
+
request.body as { raw?: string; form?: Array<{ key: string; value: string; enabled: boolean }>; graphql?: { query: string; variables?: string } } | null,
|
|
48
|
+
request.bodyType,
|
|
49
|
+
request.auth as AuthConfig | null,
|
|
50
|
+
variables,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Execute
|
|
54
|
+
const response = await executeRequest({
|
|
55
|
+
method: built.method,
|
|
56
|
+
url: built.url,
|
|
57
|
+
headers: built.headers,
|
|
58
|
+
body: built.body,
|
|
59
|
+
timeout: 30000,
|
|
60
|
+
followRedirects: true,
|
|
61
|
+
maxRedirects: 5,
|
|
62
|
+
verifySSL: true,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Save to history
|
|
66
|
+
await prisma.history.create({
|
|
67
|
+
data: {
|
|
68
|
+
requestId: id,
|
|
69
|
+
method: request.method,
|
|
70
|
+
url: built.url,
|
|
71
|
+
status: response.status,
|
|
72
|
+
statusText: response.statusText,
|
|
73
|
+
headers: response.headers,
|
|
74
|
+
body: response.body,
|
|
75
|
+
bodySize: response.bodySize,
|
|
76
|
+
timing: response.timing,
|
|
77
|
+
duration: response.timing.total,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return success(response) as Response;
|
|
82
|
+
} catch (err) {
|
|
83
|
+
return error(err) as Response;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { prisma } from '@/lib/db/client';
|
|
3
|
+
import { success, error } from '@/lib/utils/api-response';
|
|
4
|
+
|
|
5
|
+
interface RouteContext {
|
|
6
|
+
params: { id: string };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function GET(_req: NextRequest, context: RouteContext): Promise<Response> {
|
|
10
|
+
try {
|
|
11
|
+
const { id } = context.params;
|
|
12
|
+
|
|
13
|
+
const history = await prisma.history.findMany({
|
|
14
|
+
where: { requestId: id },
|
|
15
|
+
orderBy: { createdAt: 'desc' },
|
|
16
|
+
take: 50,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return success(history) as Response;
|
|
20
|
+
} catch (err) {
|
|
21
|
+
return error(err) as Response;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { prisma } from '@/lib/db/client';
|
|
3
|
+
import { success, noContent, error } from '@/lib/utils/api-response';
|
|
4
|
+
import { NotFoundError } from '@/lib/utils/errors';
|
|
5
|
+
import { UpdateRequestSchema } from '@/lib/utils/validation';
|
|
6
|
+
|
|
7
|
+
interface RouteContext {
|
|
8
|
+
params: { id: string };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function GET(_req: NextRequest, context: RouteContext): Promise<Response> {
|
|
12
|
+
try {
|
|
13
|
+
const { id } = context.params;
|
|
14
|
+
const request = await prisma.request.findUnique({
|
|
15
|
+
where: { id },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (!request) {
|
|
19
|
+
throw new NotFoundError('Request', id);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return success(request) as Response;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
return error(err) as Response;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function PUT(req: NextRequest, context: RouteContext): Promise<Response> {
|
|
29
|
+
try {
|
|
30
|
+
const { id } = context.params;
|
|
31
|
+
const body = await req.json();
|
|
32
|
+
const parsed = UpdateRequestSchema.parse({ ...body, id });
|
|
33
|
+
|
|
34
|
+
const request = await prisma.request.update({
|
|
35
|
+
where: { id },
|
|
36
|
+
data: {
|
|
37
|
+
name: parsed.name,
|
|
38
|
+
description: parsed.description,
|
|
39
|
+
method: parsed.method,
|
|
40
|
+
url: parsed.url,
|
|
41
|
+
headers: parsed.headers,
|
|
42
|
+
params: parsed.params,
|
|
43
|
+
body: parsed.body,
|
|
44
|
+
bodyType: parsed.bodyType,
|
|
45
|
+
auth: parsed.auth,
|
|
46
|
+
preRequestScript: parsed.preRequestScript,
|
|
47
|
+
tests: parsed.tests,
|
|
48
|
+
sortOrder: parsed.sortOrder,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return success(request) as Response;
|
|
53
|
+
} catch (err) {
|
|
54
|
+
return error(err) as Response;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function DELETE(_req: NextRequest, context: RouteContext): Promise<Response> {
|
|
59
|
+
try {
|
|
60
|
+
const { id } = context.params;
|
|
61
|
+
await prisma.request.delete({ where: { id } });
|
|
62
|
+
return noContent() as Response;
|
|
63
|
+
} catch (err) {
|
|
64
|
+
return error(err) as Response;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { prisma } from '@/lib/db/client';
|
|
3
|
+
import { success, created, error } from '@/lib/utils/api-response';
|
|
4
|
+
import { CreateRequestSchema } from '@/lib/utils/validation';
|
|
5
|
+
|
|
6
|
+
export async function GET(req: NextRequest): Promise<Response> {
|
|
7
|
+
try {
|
|
8
|
+
const { searchParams } = new URL(req.url);
|
|
9
|
+
const collectionId = searchParams.get('collectionId');
|
|
10
|
+
|
|
11
|
+
const requests = await prisma.request.findMany({
|
|
12
|
+
where: collectionId ? { collectionId } : undefined,
|
|
13
|
+
orderBy: { sortOrder: 'asc' },
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return success(requests) as Response;
|
|
17
|
+
} catch (err) {
|
|
18
|
+
return error(err) as Response;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function POST(req: NextRequest): Promise<Response> {
|
|
23
|
+
try {
|
|
24
|
+
const body = await req.json();
|
|
25
|
+
const parsed = CreateRequestSchema.parse(body);
|
|
26
|
+
|
|
27
|
+
const request = await prisma.request.create({
|
|
28
|
+
data: {
|
|
29
|
+
name: parsed.name,
|
|
30
|
+
description: parsed.description,
|
|
31
|
+
collectionId: parsed.collectionId,
|
|
32
|
+
method: parsed.method,
|
|
33
|
+
url: parsed.url,
|
|
34
|
+
headers: parsed.headers,
|
|
35
|
+
params: parsed.params,
|
|
36
|
+
body: parsed.body,
|
|
37
|
+
bodyType: parsed.bodyType,
|
|
38
|
+
auth: parsed.auth,
|
|
39
|
+
preRequestScript: parsed.preRequestScript,
|
|
40
|
+
tests: parsed.tests,
|
|
41
|
+
sortOrder: parsed.sortOrder,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return created(request) as Response;
|
|
46
|
+
} catch (err) {
|
|
47
|
+
return error(err) as Response;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { prisma } from '@/lib/db/client';
|
|
3
|
+
import { success, created, error } from '@/lib/utils/api-response';
|
|
4
|
+
import { CreateWorkspaceSchema } from '@/lib/utils/validation';
|
|
5
|
+
|
|
6
|
+
export async function GET(): Promise<Response> {
|
|
7
|
+
try {
|
|
8
|
+
const workspaces = await prisma.workspace.findMany({
|
|
9
|
+
include: {
|
|
10
|
+
collections: { orderBy: { sortOrder: 'asc' } },
|
|
11
|
+
environments: { orderBy: { createdAt: 'asc' } },
|
|
12
|
+
},
|
|
13
|
+
orderBy: { updatedAt: 'desc' },
|
|
14
|
+
});
|
|
15
|
+
return success(workspaces) as Response;
|
|
16
|
+
} catch (err) {
|
|
17
|
+
return error(err) as Response;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function POST(req: NextRequest): Promise<Response> {
|
|
22
|
+
try {
|
|
23
|
+
const body = await req.json();
|
|
24
|
+
const parsed = CreateWorkspaceSchema.parse(body);
|
|
25
|
+
|
|
26
|
+
const workspace = await prisma.workspace.create({
|
|
27
|
+
data: {
|
|
28
|
+
name: parsed.name,
|
|
29
|
+
description: parsed.description,
|
|
30
|
+
userId: parsed.userId,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return created(workspace) as Response;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return error(err) as Response;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
:root {
|
|
7
|
+
--background: 0 0% 100%;
|
|
8
|
+
--foreground: 222.2 84% 4.9%;
|
|
9
|
+
--card: 0 0% 100%;
|
|
10
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
11
|
+
--popover: 0 0% 100%;
|
|
12
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
13
|
+
--primary: 221.2 83.2% 53.3%;
|
|
14
|
+
--primary-foreground: 210 40% 98%;
|
|
15
|
+
--secondary: 210 40% 96.1%;
|
|
16
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
17
|
+
--muted: 210 40% 96.1%;
|
|
18
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
19
|
+
--accent: 210 40% 96.1%;
|
|
20
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
21
|
+
--destructive: 0 84.2% 60.2%;
|
|
22
|
+
--destructive-foreground: 210 40% 98%;
|
|
23
|
+
--border: 214.3 31.8% 91.4%;
|
|
24
|
+
--input: 214.3 31.8% 91.4%;
|
|
25
|
+
--ring: 221.2 83.2% 53.3%;
|
|
26
|
+
--radius: 0.5rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dark {
|
|
30
|
+
--background: 222.2 84% 4.9%;
|
|
31
|
+
--foreground: 210 40% 98%;
|
|
32
|
+
--card: 222.2 84% 4.9%;
|
|
33
|
+
--card-foreground: 210 40% 98%;
|
|
34
|
+
--popover: 222.2 84% 4.9%;
|
|
35
|
+
--popover-foreground: 210 40% 98%;
|
|
36
|
+
--primary: 217.2 91.2% 59.8%;
|
|
37
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
38
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
39
|
+
--secondary-foreground: 210 40% 98%;
|
|
40
|
+
--muted: 217.2 32.6% 17.5%;
|
|
41
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
42
|
+
--accent: 217.2 32.6% 17.5%;
|
|
43
|
+
--accent-foreground: 210 40% 98%;
|
|
44
|
+
--destructive: 0 62.8% 30.6%;
|
|
45
|
+
--destructive-foreground: 210 40% 98%;
|
|
46
|
+
--border: 217.2 32.6% 17.5%;
|
|
47
|
+
--input: 217.2 32.6% 17.5%;
|
|
48
|
+
--ring: 224.3 76.3% 48%;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@layer base {
|
|
53
|
+
* {
|
|
54
|
+
@apply border-border;
|
|
55
|
+
}
|
|
56
|
+
body {
|
|
57
|
+
@apply bg-background text-foreground;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Monaco editor overrides */
|
|
62
|
+
.monaco-editor .margin,
|
|
63
|
+
.monaco-editor,
|
|
64
|
+
.monaco-editor-background,
|
|
65
|
+
.monaco-editor .inputarea.ime-input {
|
|
66
|
+
background-color: transparent !important;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Custom scrollbar */
|
|
70
|
+
::-webkit-scrollbar {
|
|
71
|
+
width: 8px;
|
|
72
|
+
height: 8px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
::-webkit-scrollbar-track {
|
|
76
|
+
@apply bg-transparent;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
::-webkit-scrollbar-thumb {
|
|
80
|
+
@apply bg-border rounded-full;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
::-webkit-scrollbar-thumb:hover {
|
|
84
|
+
@apply bg-muted-foreground/50;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Split pane resize handle */
|
|
88
|
+
.resize-handle {
|
|
89
|
+
@apply relative;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.resize-handle::after {
|
|
93
|
+
content: '';
|
|
94
|
+
@apply absolute inset-0 transition-colors;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.resize-handle:hover::after {
|
|
98
|
+
@apply bg-primary/20;
|
|
99
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { Inter } from 'next/font/google';
|
|
3
|
+
import './globals.css';
|
|
4
|
+
|
|
5
|
+
const inter = Inter({ subsets: ['latin'] });
|
|
6
|
+
|
|
7
|
+
export const metadata: Metadata = {
|
|
8
|
+
title: 'APITester - Open-Source API Testing Tool',
|
|
9
|
+
description: 'A beautiful, open-source API testing tool with AI-powered features. The Postman alternative built for modern developers.',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default function RootLayout({
|
|
13
|
+
children,
|
|
14
|
+
}: {
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
}) {
|
|
17
|
+
return (
|
|
18
|
+
<html lang="en" className="dark">
|
|
19
|
+
<body className={inter.className}>
|
|
20
|
+
{children}
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
23
|
+
);
|
|
24
|
+
}
|