ta_data_mas 0.0.2
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 +42 -0
- package/apps/api/.prettierrc +4 -0
- package/apps/api/README.md +98 -0
- package/apps/api/drizzle/0000_right_drax.sql +87 -0
- package/apps/api/drizzle/meta/0000_snapshot.json +588 -0
- package/apps/api/drizzle/meta/_journal.json +13 -0
- package/apps/api/drizzle.config.ts +12 -0
- package/apps/api/eslint.config.mjs +35 -0
- package/apps/api/nest-cli.json +8 -0
- package/apps/api/package.json +99 -0
- package/apps/api/src/app.controller.spec.ts +25 -0
- package/apps/api/src/app.controller.ts +9 -0
- package/apps/api/src/app.module.ts +37 -0
- package/apps/api/src/app.service.ts +8 -0
- package/apps/api/src/auth/auth.controller.ts +113 -0
- package/apps/api/src/auth/auth.module.ts +15 -0
- package/apps/api/src/auth/auth.service.ts +309 -0
- package/apps/api/src/auth/decorators/current-user-decorator.ts +10 -0
- package/apps/api/src/auth/decorators/require-org-role-decorator.ts +7 -0
- package/apps/api/src/auth/dto/login.dto.ts +10 -0
- package/apps/api/src/auth/dto/register.dto.ts +14 -0
- package/apps/api/src/auth/guards/jwt-auth-guard.ts +40 -0
- package/apps/api/src/auth/guards/org-role.guards.ts +60 -0
- package/apps/api/src/db/db.module.ts +9 -0
- package/apps/api/src/db/drizzle.service.ts +16 -0
- package/apps/api/src/db/schema/index.ts +6 -0
- package/apps/api/src/db/schema/organizations.ts +30 -0
- package/apps/api/src/db/schema/projects.ts +26 -0
- package/apps/api/src/db/schema/query-history.ts +16 -0
- package/apps/api/src/db/schema/storage-buckets.ts +17 -0
- package/apps/api/src/db/schema/storage-objects.ts +18 -0
- package/apps/api/src/db/schema/users.ts +14 -0
- package/apps/api/src/main.ts +51 -0
- package/apps/api/src/members/dto/invite-member-dto.ts +6 -0
- package/apps/api/src/members/dto/update-role.dto.ts +8 -0
- package/apps/api/src/members/invite.service.ts +147 -0
- package/apps/api/src/members/members.controller.ts +56 -0
- package/apps/api/src/members/members.module.ts +13 -0
- package/apps/api/src/members/members.service.ts +85 -0
- package/apps/api/src/orgs/dto/create-org.dto.ts +8 -0
- package/apps/api/src/orgs/orgs.controller.ts +27 -0
- package/apps/api/src/orgs/orgs.module.ts +12 -0
- package/apps/api/src/orgs/orgs.service.ts +86 -0
- package/apps/api/src/project-api/project-api.controller.ts +95 -0
- package/apps/api/src/project-api/project-api.module.ts +12 -0
- package/apps/api/src/project-api/project-api.service.ts +196 -0
- package/apps/api/src/project-api/project-key.guard.ts +45 -0
- package/apps/api/src/project-api/query-parser.ts +129 -0
- package/apps/api/src/project-auth/dto/magic-link.dto.ts +7 -0
- package/apps/api/src/project-auth/dto/signin.dto.ts +10 -0
- package/apps/api/src/project-auth/dto/signup.dto.ts +11 -0
- package/apps/api/src/project-auth/project-auth-dashboard.controller.ts +35 -0
- package/apps/api/src/project-auth/project-auth.controller.ts +111 -0
- package/apps/api/src/project-auth/project-auth.module.ts +13 -0
- package/apps/api/src/project-auth/project-auth.service.ts +440 -0
- package/apps/api/src/projects/dto/create-project.dto.ts +9 -0
- package/apps/api/src/projects/projects.controller.ts +34 -0
- package/apps/api/src/projects/projects.module.ts +12 -0
- package/apps/api/src/projects/projects.service.ts +136 -0
- package/apps/api/src/realtime/realtime-socket.type.ts +14 -0
- package/apps/api/src/realtime/realtime.controller.ts +63 -0
- package/apps/api/src/realtime/realtime.gateway.ts +150 -0
- package/apps/api/src/realtime/realtime.module.ts +14 -0
- package/apps/api/src/realtime/realtime.service.ts +101 -0
- package/apps/api/src/realtime/trigger.service.ts +95 -0
- package/apps/api/src/sql-editor/dto/execute-query.dto.ts +7 -0
- package/apps/api/src/sql-editor/sql-editor.controller.ts +28 -0
- package/apps/api/src/sql-editor/sql-editor.module.ts +12 -0
- package/apps/api/src/sql-editor/sql-editor.service.ts +154 -0
- package/apps/api/src/storage/project-storage.controller.ts +140 -0
- package/apps/api/src/storage/storage.controller.ts +118 -0
- package/apps/api/src/storage/storage.module.ts +15 -0
- package/apps/api/src/storage/storage.service.ts +191 -0
- package/apps/api/src/storage/uploadthing.ts +23 -0
- package/apps/api/src/table-editor/dto/alter-table.dto.ts +15 -0
- package/apps/api/src/table-editor/dto/create-table.dto.ts +54 -0
- package/apps/api/src/table-editor/table-editor.controller.ts +117 -0
- package/apps/api/src/table-editor/table-editor.module.ts +12 -0
- package/apps/api/src/table-editor/table-editor.service.ts +366 -0
- package/apps/api/test/app.e2e-spec.ts +29 -0
- package/apps/api/test/jest-e2e.json +9 -0
- package/apps/api/tsconfig.build.json +4 -0
- package/apps/api/tsconfig.json +25 -0
- package/apps/web/.env.example +12 -0
- package/apps/web/AGENTS.md +9 -0
- package/apps/web/CLAUDE.md +1 -0
- package/apps/web/README.md +36 -0
- package/apps/web/components.json +25 -0
- package/apps/web/eslint.config.mjs +18 -0
- package/apps/web/next.config.ts +11 -0
- package/apps/web/package.json +48 -0
- package/apps/web/postcss.config.mjs +7 -0
- package/apps/web/public/file.svg +1 -0
- package/apps/web/public/globe.svg +1 -0
- package/apps/web/public/logo.svg +4 -0
- package/apps/web/public/next.svg +1 -0
- package/apps/web/public/vercel.svg +1 -0
- package/apps/web/public/window.svg +1 -0
- package/apps/web/src/app/(dashboard)/layout.tsx +33 -0
- package/apps/web/src/app/(dashboard)/organizations/(list)/layout.tsx +7 -0
- package/apps/web/src/app/(dashboard)/organizations/(list)/new/page.tsx +26 -0
- package/apps/web/src/app/(dashboard)/organizations/(list)/page.tsx +44 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/api/page.tsx +13 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/layout.tsx +19 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/page.tsx +10 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/providers/page.tsx +19 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/url-configuration/page.tsx +24 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/users/page.tsx +13 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/database/page.tsx +29 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/realtime/page.tsx +28 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/sql/page.tsx +21 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/storage/page.tsx +28 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/layout.tsx +20 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/projects/page.tsx +62 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/settings/members/page.tsx +28 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/settings/page.tsx +10 -0
- package/apps/web/src/app/(dashboard)/settings/page.tsx +12 -0
- package/apps/web/src/app/dashboard/page.tsx +14 -0
- package/apps/web/src/app/favicon.ico +0 -0
- package/apps/web/src/app/globals.css +115 -0
- package/apps/web/src/app/layout.tsx +36 -0
- package/apps/web/src/app/login/page.tsx +199 -0
- package/apps/web/src/app/page.tsx +100 -0
- package/apps/web/src/app/register/page.tsx +218 -0
- package/apps/web/src/components/app-sidebar.tsx +301 -0
- package/apps/web/src/components/navbar.tsx +63 -0
- package/apps/web/src/components/topNav.tsx +68 -0
- package/apps/web/src/components/ui/avatar.tsx +112 -0
- package/apps/web/src/components/ui/badge.tsx +48 -0
- package/apps/web/src/components/ui/button.tsx +66 -0
- package/apps/web/src/components/ui/card.tsx +102 -0
- package/apps/web/src/components/ui/checkbox.tsx +32 -0
- package/apps/web/src/components/ui/dialog.tsx +168 -0
- package/apps/web/src/components/ui/dropdown-menu.tsx +269 -0
- package/apps/web/src/components/ui/field.tsx +238 -0
- package/apps/web/src/components/ui/input.tsx +19 -0
- package/apps/web/src/components/ui/label.tsx +24 -0
- package/apps/web/src/components/ui/resizable.tsx +50 -0
- package/apps/web/src/components/ui/scroll-area.tsx +55 -0
- package/apps/web/src/components/ui/select.tsx +192 -0
- package/apps/web/src/components/ui/separator.tsx +28 -0
- package/apps/web/src/components/ui/sheet.tsx +147 -0
- package/apps/web/src/components/ui/sidebar.tsx +703 -0
- package/apps/web/src/components/ui/skeleton.tsx +13 -0
- package/apps/web/src/components/ui/switch.tsx +33 -0
- package/apps/web/src/components/ui/table.tsx +116 -0
- package/apps/web/src/components/ui/tooltip.tsx +57 -0
- package/apps/web/src/features/api-docs/api-docs-client.tsx +227 -0
- package/apps/web/src/features/api-docs/api-docs-helpers.server.ts +86 -0
- package/apps/web/src/features/auth/action.ts +67 -0
- package/apps/web/src/features/auth/client.schema.ts +13 -0
- package/apps/web/src/features/auth/constants.ts +6 -0
- package/apps/web/src/features/auth/cookies.ts +86 -0
- package/apps/web/src/features/auth/server.schema.ts +11 -0
- package/apps/web/src/features/auth/sign-out.ts +17 -0
- package/apps/web/src/features/members/action.ts +66 -0
- package/apps/web/src/features/members/client.schema.ts +11 -0
- package/apps/web/src/features/members/constants.ts +8 -0
- package/apps/web/src/features/members/members-client.tsx +198 -0
- package/apps/web/src/features/members/members-helpers.server.ts +21 -0
- package/apps/web/src/features/members/server.schema.ts +19 -0
- package/apps/web/src/features/organization/action.ts +45 -0
- package/apps/web/src/features/organization/client.schema.ts +5 -0
- package/apps/web/src/features/organization/constants.tsx +3 -0
- package/apps/web/src/features/organization/create-org-form.tsx +66 -0
- package/apps/web/src/features/organization/organization-helpers.server.ts +35 -0
- package/apps/web/src/features/organization/server.schema.ts +10 -0
- package/apps/web/src/features/project-auth/project-auth-action.ts +78 -0
- package/apps/web/src/features/project-auth/project-auth-client.schema.ts +18 -0
- package/apps/web/src/features/project-auth/project-auth-constants.ts +8 -0
- package/apps/web/src/features/project-auth/project-auth-helpers.server.ts +39 -0
- package/apps/web/src/features/project-auth/project-auth-providers.tsx +222 -0
- package/apps/web/src/features/project-auth/project-auth-server.schema.ts +22 -0
- package/apps/web/src/features/project-auth/project-auth-shell.tsx +106 -0
- package/apps/web/src/features/project-auth/project-auth-url-config.tsx +151 -0
- package/apps/web/src/features/project-auth/project-auth-users.tsx +138 -0
- package/apps/web/src/features/projects/action.ts +46 -0
- package/apps/web/src/features/projects/client.schema.ts +9 -0
- package/apps/web/src/features/projects/constants.ts +6 -0
- package/apps/web/src/features/projects/create-project-modal.tsx +117 -0
- package/apps/web/src/features/projects/project-helpers.server.ts +38 -0
- package/apps/web/src/features/projects/server.schema.ts +10 -0
- package/apps/web/src/features/realtime/realtime-client.tsx +294 -0
- package/apps/web/src/features/realtime/realtime-helpers.server.ts +40 -0
- package/apps/web/src/features/sql-editor/sql-editor-client.tsx +324 -0
- package/apps/web/src/features/sql-editor/sql-editor-helpers.server.ts +22 -0
- package/apps/web/src/features/storage/storage-client.tsx +500 -0
- package/apps/web/src/features/storage/storage-helpers.server.ts +36 -0
- package/apps/web/src/features/table-editor/action.ts +97 -0
- package/apps/web/src/features/table-editor/add-column-dialog.tsx +184 -0
- package/apps/web/src/features/table-editor/client.schema.ts +36 -0
- package/apps/web/src/features/table-editor/create-table-drawer.tsx +542 -0
- package/apps/web/src/features/table-editor/server.schema.ts +41 -0
- package/apps/web/src/features/table-editor/table-editor-client.tsx +737 -0
- package/apps/web/src/features/table-editor/table-editor-helpers.server.ts +39 -0
- package/apps/web/src/hooks/use-mobile.ts +19 -0
- package/apps/web/src/lib/axios.ts +8 -0
- package/apps/web/src/lib/utils.ts +6 -0
- package/apps/web/src/proxy.ts +72 -0
- package/apps/web/src/server-utils/utils.ts +10 -0
- package/apps/web/tsconfig.json +34 -0
- package/package.json +22 -0
- package/packages/apiDatabase-js/package.json +39 -0
- package/packages/apiDatabase-js/src/auth/index.ts +121 -0
- package/packages/apiDatabase-js/src/client.ts +25 -0
- package/packages/apiDatabase-js/src/db/index.ts +15 -0
- package/packages/apiDatabase-js/src/db/query-builder.ts +194 -0
- package/packages/apiDatabase-js/src/index.ts +16 -0
- package/packages/apiDatabase-js/src/realtime/index.ts +84 -0
- package/packages/apiDatabase-js/src/storage/index.ts +130 -0
- package/packages/apiDatabase-js/tsconfig.json +13 -0
- package/packages/apiDatabase-js/tsdown.config.ts +20 -0
- package/packages/constants/package.json +6 -0
- package/packages/constants/src/index.ts +76 -0
- package/packages/tsconfig.json +13 -0
- package/packages/tsdown.config.ts +20 -0
- package/packages/types/package.json +6 -0
- package/packages/types/src/index.ts +254 -0
- package/pnpm-workspace.yaml +10 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Body,
|
|
3
|
+
Controller,
|
|
4
|
+
Delete,
|
|
5
|
+
Get,
|
|
6
|
+
Param,
|
|
7
|
+
Patch,
|
|
8
|
+
Post,
|
|
9
|
+
Query,
|
|
10
|
+
UseGuards,
|
|
11
|
+
} from '@nestjs/common';
|
|
12
|
+
import { TableEditorService } from './table-editor.service';
|
|
13
|
+
import { CreateTableDto } from './dto/create-table.dto';
|
|
14
|
+
import { AddColumnDto } from './dto/alter-table.dto';
|
|
15
|
+
import { JwtAuthGuard } from '../auth/guards/jwt-auth-guard';
|
|
16
|
+
import { OrgRoleGuard } from '../auth/guards/org-role.guards';
|
|
17
|
+
|
|
18
|
+
@Controller('orgs/:slug/projects/:projectSlug/tables')
|
|
19
|
+
@UseGuards(JwtAuthGuard, OrgRoleGuard)
|
|
20
|
+
export class TableEditorController {
|
|
21
|
+
constructor(private tableEditorService: TableEditorService) {}
|
|
22
|
+
|
|
23
|
+
@Get()
|
|
24
|
+
getTables(
|
|
25
|
+
@Param('slug') slug: string,
|
|
26
|
+
@Param('projectSlug') projectSlug: string,
|
|
27
|
+
) {
|
|
28
|
+
return this.tableEditorService.getTables(slug, projectSlug);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Get(':tableName/rows')
|
|
32
|
+
getTableRows(
|
|
33
|
+
@Param('slug') slug: string,
|
|
34
|
+
@Param('projectSlug') projectSlug: string,
|
|
35
|
+
@Param('tableName') tableName: string,
|
|
36
|
+
@Query('limit') limit?: string,
|
|
37
|
+
@Query('offset') offset?: string,
|
|
38
|
+
) {
|
|
39
|
+
return this.tableEditorService.getTableRows(
|
|
40
|
+
slug,
|
|
41
|
+
projectSlug,
|
|
42
|
+
tableName,
|
|
43
|
+
limit ? parseInt(limit, 10) : 100,
|
|
44
|
+
offset ? parseInt(offset, 10) : 0,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Get(':tableName')
|
|
49
|
+
getTableInfo(
|
|
50
|
+
@Param('slug') slug: string,
|
|
51
|
+
@Param('projectSlug') projectSlug: string,
|
|
52
|
+
@Param('tableName') tableName: string,
|
|
53
|
+
) {
|
|
54
|
+
return this.tableEditorService.getTableInfo(slug, projectSlug, tableName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@Post()
|
|
58
|
+
createTable(
|
|
59
|
+
@Param('slug') slug: string,
|
|
60
|
+
@Param('projectSlug') projectSlug: string,
|
|
61
|
+
@Body() dto: CreateTableDto,
|
|
62
|
+
) {
|
|
63
|
+
return this.tableEditorService.createTable(slug, projectSlug, dto);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Patch(':tableName/columns')
|
|
67
|
+
addColumn(
|
|
68
|
+
@Param('slug') slug: string,
|
|
69
|
+
@Param('projectSlug') projectSlug: string,
|
|
70
|
+
@Param('tableName') tableName: string,
|
|
71
|
+
@Body() dto: AddColumnDto,
|
|
72
|
+
) {
|
|
73
|
+
return this.tableEditorService.addColumn(slug, projectSlug, tableName, dto);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Delete(':tableName/columns/:columnName')
|
|
77
|
+
dropColumn(
|
|
78
|
+
@Param('slug') slug: string,
|
|
79
|
+
@Param('projectSlug') projectSlug: string,
|
|
80
|
+
@Param('tableName') tableName: string,
|
|
81
|
+
@Param('columnName') columnName: string,
|
|
82
|
+
) {
|
|
83
|
+
return this.tableEditorService.dropColumn(
|
|
84
|
+
slug,
|
|
85
|
+
projectSlug,
|
|
86
|
+
tableName,
|
|
87
|
+
columnName,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@Delete(':tableName')
|
|
92
|
+
deleteTable(
|
|
93
|
+
@Param('slug') slug: string,
|
|
94
|
+
@Param('projectSlug') projectSlug: string,
|
|
95
|
+
@Param('tableName') tableName: string,
|
|
96
|
+
) {
|
|
97
|
+
return this.tableEditorService.deleteTable(slug, projectSlug, tableName);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Patch(':tableName/rows/:pkValue')
|
|
101
|
+
updateRow(
|
|
102
|
+
@Param('slug') slug: string,
|
|
103
|
+
@Param('projectSlug') projectSlug: string,
|
|
104
|
+
@Param('tableName') tableName: string,
|
|
105
|
+
@Param('pkValue') pkValue: string,
|
|
106
|
+
@Body() body: { pkColumn: string; updates: Record<string, unknown> },
|
|
107
|
+
) {
|
|
108
|
+
return this.tableEditorService.updateRow(
|
|
109
|
+
slug,
|
|
110
|
+
projectSlug,
|
|
111
|
+
tableName,
|
|
112
|
+
body.pkColumn,
|
|
113
|
+
pkValue,
|
|
114
|
+
body.updates,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { AuthModule } from '../auth/auth.module';
|
|
3
|
+
import { TableEditorService } from './table-editor.service';
|
|
4
|
+
import { TableEditorController } from './table-editor.controller';
|
|
5
|
+
import { OrgRoleGuard } from 'src/auth/guards/org-role.guards';
|
|
6
|
+
|
|
7
|
+
@Module({
|
|
8
|
+
imports: [AuthModule],
|
|
9
|
+
providers: [TableEditorService, OrgRoleGuard],
|
|
10
|
+
controllers: [TableEditorController],
|
|
11
|
+
})
|
|
12
|
+
export class TableEditorModule {}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BadRequestException,
|
|
3
|
+
Injectable,
|
|
4
|
+
NotFoundException,
|
|
5
|
+
} from '@nestjs/common';
|
|
6
|
+
import { and, eq, sql } from 'drizzle-orm';
|
|
7
|
+
import { DrizzleService } from '../db/drizzle.service';
|
|
8
|
+
import { projects, organizations } from '../db/schema';
|
|
9
|
+
import { CreateTableDto } from './dto/create-table.dto';
|
|
10
|
+
import { AddColumnDto } from './dto/alter-table.dto';
|
|
11
|
+
import type { TableInfo, TableColumn, ColumnType } from '@apiDatabase/types';
|
|
12
|
+
|
|
13
|
+
@Injectable()
|
|
14
|
+
export class TableEditorService {
|
|
15
|
+
constructor(private drizzle: DrizzleService) {}
|
|
16
|
+
|
|
17
|
+
private assertSafeIdentifier(name: string, label: string): void {
|
|
18
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
19
|
+
throw new BadRequestException(`Invalid ${label}: ${name}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Type mapping
|
|
24
|
+
private mapPgTypeToColumnType(pgType: string): ColumnType {
|
|
25
|
+
const map: Record<string, ColumnType> = {
|
|
26
|
+
text: 'text',
|
|
27
|
+
'character varying': 'text',
|
|
28
|
+
integer: 'integer',
|
|
29
|
+
bigint: 'bigint',
|
|
30
|
+
boolean: 'boolean',
|
|
31
|
+
'timestamp with time zone': 'timestamp',
|
|
32
|
+
'timestamp without time zone': 'timestamp',
|
|
33
|
+
uuid: 'uuid',
|
|
34
|
+
jsonb: 'jsonb',
|
|
35
|
+
numeric: 'numeric',
|
|
36
|
+
array: 'array',
|
|
37
|
+
};
|
|
38
|
+
return map[pgType] ?? 'text';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private mapType(type: string): string {
|
|
42
|
+
const map: Record<string, string> = {
|
|
43
|
+
text: 'TEXT',
|
|
44
|
+
integer: 'INTEGER',
|
|
45
|
+
bigint: 'BIGINT',
|
|
46
|
+
boolean: 'BOOLEAN',
|
|
47
|
+
timestamp: 'TIMESTAMPTZ',
|
|
48
|
+
uuid: 'UUID',
|
|
49
|
+
jsonb: 'JSONB',
|
|
50
|
+
numeric: 'NUMERIC',
|
|
51
|
+
array: 'ARRAY',
|
|
52
|
+
};
|
|
53
|
+
return map[type] ?? 'TEXT';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private formatDefault(value: string, type: ColumnType): string {
|
|
57
|
+
const trimmed = value.trim();
|
|
58
|
+
if (!trimmed) return '';
|
|
59
|
+
|
|
60
|
+
if (/^(now\(\)|gen_random_uuid\(\)|current_timestamp)$/i.test(trimmed)) {
|
|
61
|
+
return trimmed;
|
|
62
|
+
}
|
|
63
|
+
if (type === 'boolean') return trimmed;
|
|
64
|
+
if (type === 'integer' || type === 'bigint' || type === 'numeric') {
|
|
65
|
+
return trimmed;
|
|
66
|
+
}
|
|
67
|
+
if (type === 'jsonb') return `'${trimmed.replace(/'/g, "''")}'::jsonb`;
|
|
68
|
+
return `'${trimmed.replace(/'/g, "''")}'`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private async getProjectSchema(
|
|
72
|
+
orgSlug: string,
|
|
73
|
+
projectSlug: string,
|
|
74
|
+
): Promise<string> {
|
|
75
|
+
const [row] = await this.drizzle.db
|
|
76
|
+
.select({ dbSchema: projects.dbSchema })
|
|
77
|
+
.from(projects)
|
|
78
|
+
.innerJoin(organizations, eq(projects.orgId, organizations.id))
|
|
79
|
+
.where(
|
|
80
|
+
and(eq(organizations.slug, orgSlug), eq(projects.slug, projectSlug)),
|
|
81
|
+
)
|
|
82
|
+
.limit(1);
|
|
83
|
+
|
|
84
|
+
if (!row) throw new NotFoundException('Project not found');
|
|
85
|
+
return row.dbSchema;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// List tables
|
|
89
|
+
async getTables(orgSlug: string, projectSlug: string): Promise<string[]> {
|
|
90
|
+
const schema = await this.getProjectSchema(orgSlug, projectSlug);
|
|
91
|
+
|
|
92
|
+
const result = await this.drizzle.db.execute<{ table_name: string }>(
|
|
93
|
+
`SELECT table_name
|
|
94
|
+
FROM information_schema.tables
|
|
95
|
+
WHERE table_schema = '${schema}'
|
|
96
|
+
AND table_type = 'BASE TABLE'
|
|
97
|
+
ORDER BY table_name ASC`,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return result.rows.map((r) => r.table_name);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Get table structure
|
|
104
|
+
async getTableInfo(
|
|
105
|
+
orgSlug: string,
|
|
106
|
+
projectSlug: string,
|
|
107
|
+
tableName: string,
|
|
108
|
+
): Promise<TableInfo> {
|
|
109
|
+
this.assertSafeIdentifier(tableName, 'table name');
|
|
110
|
+
const schema = await this.getProjectSchema(orgSlug, projectSlug);
|
|
111
|
+
|
|
112
|
+
const columnsResult = await this.drizzle.db.execute<{
|
|
113
|
+
column_name: string;
|
|
114
|
+
data_type: string;
|
|
115
|
+
is_nullable: string;
|
|
116
|
+
column_default: string | null;
|
|
117
|
+
}>(
|
|
118
|
+
`SELECT column_name, data_type, is_nullable, column_default
|
|
119
|
+
FROM information_schema.columns
|
|
120
|
+
WHERE table_schema = '${schema}'
|
|
121
|
+
AND table_name = '${tableName}'
|
|
122
|
+
ORDER BY ordinal_position ASC`,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const pkResult = await this.drizzle.db.execute<{ column_name: string }>(
|
|
126
|
+
`SELECT kcu.column_name
|
|
127
|
+
FROM information_schema.table_constraints tc
|
|
128
|
+
JOIN information_schema.key_column_usage kcu
|
|
129
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
130
|
+
AND tc.table_schema = kcu.table_schema
|
|
131
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
132
|
+
AND tc.table_schema = '${schema}'
|
|
133
|
+
AND tc.table_name = '${tableName}'`,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const pkColumns = new Set(pkResult.rows.map((r) => r.column_name));
|
|
137
|
+
|
|
138
|
+
const fkResult = await this.drizzle.db.execute<{
|
|
139
|
+
column_name: string;
|
|
140
|
+
foreign_table_name: string;
|
|
141
|
+
foreign_column_name: string;
|
|
142
|
+
}>(
|
|
143
|
+
`SELECT
|
|
144
|
+
kcu.column_name,
|
|
145
|
+
ccu.table_name AS foreign_table_name,
|
|
146
|
+
ccu.column_name AS foreign_column_name
|
|
147
|
+
FROM information_schema.table_constraints tc
|
|
148
|
+
JOIN information_schema.key_column_usage kcu
|
|
149
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
150
|
+
AND tc.table_schema = kcu.table_schema
|
|
151
|
+
JOIN information_schema.constraint_column_usage ccu
|
|
152
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
153
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
154
|
+
AND tc.table_schema = '${schema}'
|
|
155
|
+
AND tc.table_name = '${tableName}'`,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const fkMap = new Map(
|
|
159
|
+
fkResult.rows.map((r) => [
|
|
160
|
+
r.column_name,
|
|
161
|
+
{ table: r.foreign_table_name, column: r.foreign_column_name },
|
|
162
|
+
]),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const columns: TableColumn[] = columnsResult.rows.map((col) => ({
|
|
166
|
+
name: col.column_name,
|
|
167
|
+
type: this.mapPgTypeToColumnType(col.data_type),
|
|
168
|
+
isNullable: col.is_nullable === 'YES',
|
|
169
|
+
isPrimaryKey: pkColumns.has(col.column_name),
|
|
170
|
+
defaultValue: col.column_default,
|
|
171
|
+
foreignKey: fkMap.get(col.column_name) ?? null,
|
|
172
|
+
}));
|
|
173
|
+
|
|
174
|
+
if (columns.length === 0) {
|
|
175
|
+
throw new NotFoundException(`Table "${tableName}" not found`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { name: tableName, columns };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private isMissingTableError(err: unknown): boolean {
|
|
182
|
+
const code =
|
|
183
|
+
err &&
|
|
184
|
+
typeof err === 'object' &&
|
|
185
|
+
'cause' in err &&
|
|
186
|
+
err.cause &&
|
|
187
|
+
typeof err.cause === 'object' &&
|
|
188
|
+
'code' in err.cause
|
|
189
|
+
? String(err.cause.code)
|
|
190
|
+
: null;
|
|
191
|
+
|
|
192
|
+
return code === '42P01';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Get table rows
|
|
196
|
+
async getTableRows(
|
|
197
|
+
orgSlug: string,
|
|
198
|
+
projectSlug: string,
|
|
199
|
+
tableName: string,
|
|
200
|
+
limit = 100,
|
|
201
|
+
offset = 0,
|
|
202
|
+
): Promise<{ rows: Record<string, unknown>[]; count: number }> {
|
|
203
|
+
this.assertSafeIdentifier(tableName, 'table name');
|
|
204
|
+
const schema = await this.getProjectSchema(orgSlug, projectSlug);
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const [rowsResult, countResult] = await Promise.all([
|
|
208
|
+
this.drizzle.db.execute<Record<string, unknown>>(
|
|
209
|
+
`SELECT * FROM "${schema}"."${tableName}" LIMIT ${limit} OFFSET ${offset}`,
|
|
210
|
+
),
|
|
211
|
+
this.drizzle.db.execute<{ count: string }>(
|
|
212
|
+
`SELECT COUNT(*) as count FROM "${schema}"."${tableName}"`,
|
|
213
|
+
),
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
rows: rowsResult.rows,
|
|
218
|
+
count: parseInt(countResult.rows[0]?.count ?? '0', 10),
|
|
219
|
+
};
|
|
220
|
+
} catch (err) {
|
|
221
|
+
if (this.isMissingTableError(err)) {
|
|
222
|
+
throw new NotFoundException(`Table "${tableName}" not found`);
|
|
223
|
+
}
|
|
224
|
+
throw err;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Create table
|
|
229
|
+
async createTable(
|
|
230
|
+
orgSlug: string,
|
|
231
|
+
projectSlug: string,
|
|
232
|
+
dto: CreateTableDto,
|
|
233
|
+
): Promise<void> {
|
|
234
|
+
this.assertSafeIdentifier(dto.name, 'table name');
|
|
235
|
+
const schema = await this.getProjectSchema(orgSlug, projectSlug);
|
|
236
|
+
|
|
237
|
+
const pkCols = dto.columns.filter((c) => c.isPrimaryKey);
|
|
238
|
+
|
|
239
|
+
const columnDefs = dto.columns.map((col) => {
|
|
240
|
+
this.assertSafeIdentifier(col.name, 'column name');
|
|
241
|
+
|
|
242
|
+
if (col.foreignKeyTable) {
|
|
243
|
+
this.assertSafeIdentifier(col.foreignKeyTable, 'foreign key table');
|
|
244
|
+
}
|
|
245
|
+
if (col.foreignKeyColumn) {
|
|
246
|
+
this.assertSafeIdentifier(col.foreignKeyColumn, 'foreign key column');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const parts: string[] = [];
|
|
250
|
+
let colDef = `"${col.name}" ${this.mapType(col.type)}`;
|
|
251
|
+
|
|
252
|
+
if (col.isPrimaryKey && col.type === 'bigint' && !col.defaultValue) {
|
|
253
|
+
colDef += ' GENERATED ALWAYS AS IDENTITY';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
parts.push(colDef);
|
|
257
|
+
|
|
258
|
+
if (col.isPrimaryKey && pkCols.length === 1) parts.push('PRIMARY KEY');
|
|
259
|
+
if (!col.isNullable && !col.isPrimaryKey) parts.push('NOT NULL');
|
|
260
|
+
if (col.defaultValue) {
|
|
261
|
+
parts.push(`DEFAULT ${this.formatDefault(col.defaultValue, col.type)}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return parts.join(' ');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const pkConstraint =
|
|
268
|
+
pkCols.length > 1
|
|
269
|
+
? `PRIMARY KEY (${pkCols.map((c) => `"${c.name}"`).join(', ')})`
|
|
270
|
+
: null;
|
|
271
|
+
|
|
272
|
+
const fkConstraints = dto.columns
|
|
273
|
+
.filter((col) => col.foreignKeyTable && col.foreignKeyColumn)
|
|
274
|
+
.map(
|
|
275
|
+
(col) =>
|
|
276
|
+
`FOREIGN KEY ("${col.name}") REFERENCES "${schema}"."${col.foreignKeyTable!}" ("${col.foreignKeyColumn!}")`,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const allDefs = [
|
|
280
|
+
...columnDefs,
|
|
281
|
+
...(pkConstraint ? [pkConstraint] : []),
|
|
282
|
+
...fkConstraints,
|
|
283
|
+
].join(', ');
|
|
284
|
+
|
|
285
|
+
await this.drizzle.db.execute(
|
|
286
|
+
`CREATE TABLE "${schema}"."${dto.name}" (${allDefs})`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Delete table
|
|
291
|
+
async deleteTable(
|
|
292
|
+
orgSlug: string,
|
|
293
|
+
projectSlug: string,
|
|
294
|
+
tableName: string,
|
|
295
|
+
): Promise<void> {
|
|
296
|
+
this.assertSafeIdentifier(tableName, 'table name');
|
|
297
|
+
const schema = await this.getProjectSchema(orgSlug, projectSlug);
|
|
298
|
+
await this.drizzle.db.execute(
|
|
299
|
+
`DROP TABLE IF EXISTS "${schema}"."${tableName}"`,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Add column
|
|
304
|
+
async addColumn(
|
|
305
|
+
orgSlug: string,
|
|
306
|
+
projectSlug: string,
|
|
307
|
+
tableName: string,
|
|
308
|
+
dto: AddColumnDto,
|
|
309
|
+
): Promise<void> {
|
|
310
|
+
this.assertSafeIdentifier(tableName, 'table name');
|
|
311
|
+
this.assertSafeIdentifier(dto.name, 'column name');
|
|
312
|
+
const schema = await this.getProjectSchema(orgSlug, projectSlug);
|
|
313
|
+
|
|
314
|
+
let colDef = `"${dto.name}" ${this.mapType(dto.type)}`;
|
|
315
|
+
if (dto.defaultValue) {
|
|
316
|
+
colDef += ` DEFAULT ${this.formatDefault(dto.defaultValue, dto.type)}`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
await this.drizzle.db.execute(
|
|
320
|
+
`ALTER TABLE "${schema}"."${tableName}" ADD COLUMN ${colDef}`,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Drop column
|
|
325
|
+
async dropColumn(
|
|
326
|
+
orgSlug: string,
|
|
327
|
+
projectSlug: string,
|
|
328
|
+
tableName: string,
|
|
329
|
+
columnName: string,
|
|
330
|
+
): Promise<void> {
|
|
331
|
+
this.assertSafeIdentifier(tableName, 'table name');
|
|
332
|
+
this.assertSafeIdentifier(columnName, 'column name');
|
|
333
|
+
const schema = await this.getProjectSchema(orgSlug, projectSlug);
|
|
334
|
+
await this.drizzle.db.execute(
|
|
335
|
+
`ALTER TABLE "${schema}"."${tableName}" DROP COLUMN "${columnName}"`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Update row
|
|
340
|
+
async updateRow(
|
|
341
|
+
orgSlug: string,
|
|
342
|
+
projectSlug: string,
|
|
343
|
+
tableName: string,
|
|
344
|
+
pkColumn: string,
|
|
345
|
+
pkValue: string,
|
|
346
|
+
updates: Record<string, unknown>,
|
|
347
|
+
): Promise<void> {
|
|
348
|
+
this.assertSafeIdentifier(tableName, 'table name');
|
|
349
|
+
this.assertSafeIdentifier(pkColumn, 'primary key column');
|
|
350
|
+
Object.keys(updates).forEach((col) =>
|
|
351
|
+
this.assertSafeIdentifier(col, 'column name'),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const schema = await this.getProjectSchema(orgSlug, projectSlug);
|
|
355
|
+
|
|
356
|
+
const setFragments = Object.entries(updates).map(
|
|
357
|
+
([col, val]) => sql`${sql.identifier(col)} = ${val}`,
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
await this.drizzle.db.execute(
|
|
361
|
+
sql`UPDATE ${sql.identifier(schema)}.${sql.identifier(tableName)}
|
|
362
|
+
SET ${sql.join(setFragments, sql`, `)}
|
|
363
|
+
WHERE ${sql.identifier(pkColumn)} = ${pkValue}`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { INestApplication } from '@nestjs/common';
|
|
3
|
+
import request from 'supertest';
|
|
4
|
+
import { App } from 'supertest/types';
|
|
5
|
+
import { AppModule } from './../src/app.module';
|
|
6
|
+
|
|
7
|
+
describe('AppController (e2e)', () => {
|
|
8
|
+
let app: INestApplication<App>;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
12
|
+
imports: [AppModule],
|
|
13
|
+
}).compile();
|
|
14
|
+
|
|
15
|
+
app = moduleFixture.createNestApplication();
|
|
16
|
+
await app.init();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('/ (GET)', () => {
|
|
20
|
+
return request(app.getHttpServer())
|
|
21
|
+
.get('/')
|
|
22
|
+
.expect(200)
|
|
23
|
+
.expect('Hello World!');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(async () => {
|
|
27
|
+
await app.close();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "nodenext",
|
|
4
|
+
"moduleResolution": "nodenext",
|
|
5
|
+
"resolvePackageJsonExports": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"isolatedModules": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"removeComments": true,
|
|
10
|
+
"emitDecoratorMetadata": true,
|
|
11
|
+
"experimentalDecorators": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"target": "ES2023",
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"outDir": "./dist",
|
|
16
|
+
"baseUrl": "./",
|
|
17
|
+
"incremental": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"strictNullChecks": true,
|
|
20
|
+
"forceConsistentCasingInFileNames": true,
|
|
21
|
+
"noImplicitAny": false,
|
|
22
|
+
"strictBindCallApply": false,
|
|
23
|
+
"noFallthroughCasesInSwitch": false
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# NestJS API (include /api prefix)
|
|
2
|
+
# Used by Server Components, Server Actions, and the /api/proxy route
|
|
3
|
+
API_URL=http://localhost:3000/api
|
|
4
|
+
|
|
5
|
+
# Browser-facing API base (local = full URL; production can be /api/proxy)
|
|
6
|
+
NEXT_PUBLIC_API_URL=http://localhost:3000/api
|
|
7
|
+
|
|
8
|
+
# Optional — where users land after OAuth (defaults to http://localhost:3001)
|
|
9
|
+
NEXT_PUBLIC_WEB_URL=http://localhost:3001
|
|
10
|
+
|
|
11
|
+
# Optional — WebSocket origin for realtime (defaults to API host without /api)
|
|
12
|
+
# NEXT_PUBLIC_REALTIME_URL=http://localhost:3000
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- BEGIN:nextjs-agent-rules -->
|
|
2
|
+
|
|
3
|
+
# This is NOT the Next.js you know
|
|
4
|
+
|
|
5
|
+
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
|
|
6
|
+
|
|
7
|
+
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
|
8
|
+
|
|
9
|
+
<!-- END:nextjs-agent-rules -->
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
First, run the development server:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
# or
|
|
10
|
+
yarn dev
|
|
11
|
+
# or
|
|
12
|
+
pnpm dev
|
|
13
|
+
# or
|
|
14
|
+
bun dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
18
|
+
|
|
19
|
+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
|
20
|
+
|
|
21
|
+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
|
22
|
+
|
|
23
|
+
## Learn More
|
|
24
|
+
|
|
25
|
+
To learn more about Next.js, take a look at the following resources:
|
|
26
|
+
|
|
27
|
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
28
|
+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
29
|
+
|
|
30
|
+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
31
|
+
|
|
32
|
+
## Deploy on Vercel
|
|
33
|
+
|
|
34
|
+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
35
|
+
|
|
36
|
+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "radix-nova",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/app/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"rtl": false,
|
|
15
|
+
"aliases": {
|
|
16
|
+
"components": "@/components",
|
|
17
|
+
"utils": "@/lib/utils",
|
|
18
|
+
"ui": "@/components/ui",
|
|
19
|
+
"lib": "@/lib",
|
|
20
|
+
"hooks": "@/hooks"
|
|
21
|
+
},
|
|
22
|
+
"menuColor": "default",
|
|
23
|
+
"menuAccent": "subtle",
|
|
24
|
+
"registries": {}
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
".next/**",
|
|
12
|
+
"out/**",
|
|
13
|
+
"build/**",
|
|
14
|
+
"next-env.d.ts",
|
|
15
|
+
]),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export default eslintConfig;
|