Compare commits

...

46 Commits

Author SHA1 Message Date
chyoo 9f67ee5719 fix(admin): filter non-container releases from deployments
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-13 17:53:48 +09:00
chyoo a6a4426249 add instructor invite code..
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-07 11:57:55 +09:00
chyoo 8b7ec648cd add tailscale node delete!
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-07 10:25:49 +09:00
chyoo 8151ec094d feat: display platform agent status and version
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-06 16:03:26 +09:00
chyoo 63e85d5306 fix: exclude database components from app releases and deployments 2026-07-15 16:10:36 +09:00
chyoo 3945e42c53 feat: add quick deploy for launcher backend builds
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-15 16:01:09 +09:00
chyoo 53b4909df7 fix: add lms web verision url
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-15 14:55:19 +09:00
chyoo a80e6eab20 fix: apply deployment resource settings to Kubernetes workloads
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-15 14:28:36 +09:00
chyoo 1c240e895b fix: install native build tools for launcher client builds
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-15 14:15:05 +09:00
chyoo 03d065bccb chore: remove stale generated prisma clients
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-14 15:13:39 +09:00
chyoo f4a748b5f3 improve runtime environment variable modal usability
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-14 15:08:50 +09:00
chyoo d922b873e4 add infrastructure resource YAML viewer and fix PVC visibility
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-14 13:35:59 +09:00
chyoo 78f3578098 slim admin backend image and remove compose config
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-14 13:15:21 +09:00
chyoo e811208773 fix: use admin R2 avatars for operator activity..
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-14 11:51:43 +09:00
chyoo cd4bdbecd7 add manual manage..
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-14 10:45:31 +09:00
chyoo e31a07f5d9 add admin issue..
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-13 17:14:19 +09:00
chyoo 29abcca57e launcher deploy modify..
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-13 17:02:51 +09:00
chyoo 195a893bd1 modify nav..
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-13 16:56:13 +09:00
chyoo aac1d01526 add terms management for signup agreements
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 14:21:45 +09:00
chyoo e8ea575182 improve license policies and launcher display settings
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 13:40:09 +09:00
chyoo ada3309db4 manage license app launch policies
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 13:14:29 +09:00
chyoo 6311f8a553 show notice creators with admin avatars
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 11:11:32 +09:00
chyoo 609c345739 link admin avatars by email-backed auth users
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 10:55:04 +09:00
chyoo 3873ce3169 track requesters for builds releases deployments and migrations
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-10 10:43:02 +09:00
chyoo dd09471267 add runtime profile deletion and refine config forms
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 14:53:45 +09:00
chyoo 3a79364905 improve app registration and component service ports
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 14:26:49 +09:00
chyoo c46c176ca8 refine app management and source repository displays
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 13:50:45 +09:00
chyoo fbefc08b85 add app monitoring charts and runtime metrics
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 13:09:41 +09:00
chyoo f142de8763 load full log
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 12:54:10 +09:00
chyoo b191dc9f03 refine dashboard infrastructure and resource status
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 11:56:05 +09:00
chyoo c1d424b13c refine common app pipelines and user avatars
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 11:27:45 +09:00
chyoo ca35c30f87 add avatar user list
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 11:20:23 +09:00
chyoo dceb20b26a improve common app pipeline history layout
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 11:01:39 +09:00
chyoo 76cc887371 paginate common app pipeline history by repository
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 10:53:53 +09:00
chyoo b35c486ae9 show common app pipeline sha and duration
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 10:23:37 +09:00
chyoo c2f834e92d improve admin release and deployment safeguards
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 09:46:43 +09:00
chyoo da30d88688 admin appPermissions
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 09:13:01 +09:00
chyoo eb79bddcc0 admin appPermissions
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-09 09:07:03 +09:00
chyoo beb9dda8c6 connect tailscale node status through platform gateway
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 18:14:02 +09:00
chyoo 27bf41d2d3 connect tailscale node status through platform gateway
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 17:51:27 +09:00
chyoo 5a2041ddd3 connect tailscale node status through platform gateway
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 17:22:46 +09:00
chyoo fddf70c985 connect tailscale node status through platform gateway
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 17:13:52 +09:00
chyoo 52d53b0462 connect tailscale node status through platform gateway
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 16:39:20 +09:00
chyoo ec17bba92d add launcher component secret setting support
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 15:24:15 +09:00
chyoo e74c296b7e harden release promotion fallback on conflict
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-07 18:14:35 +09:00
chyoo ce99890dc8 harden release promotion fallback on conflict 2026-07-07 18:14:12 +09:00
127 changed files with 4188 additions and 24676 deletions
+6 -2
View File
@@ -8,11 +8,15 @@ RUN npm run build
FROM node:22-bookworm-slim AS runner
WORKDIR /app
ENV NODE_ENV=production
ARG DOCKER_VERSION=27.5.1
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl git docker.io binutils \
&& apt-get install -y --no-install-recommends ca-certificates curl git python3 make g++ \
&& curl -fsSL "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz" \
| tar -xz -C /tmp \
&& install -m 0755 /tmp/docker/docker /usr/local/bin/docker \
&& curl -fsSL -o /usr/local/bin/kubectl "https://dl.k8s.io/release/v1.30.0/bin/linux/amd64/kubectl" \
&& chmod 0755 /usr/local/bin/kubectl \
&& rm -rf /var/lib/apt/lists/*
&& rm -rf /tmp/docker /var/lib/apt/lists/*
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
-21
View File
@@ -1,21 +0,0 @@
services:
postgres:
image: postgres:17-alpine
container_name: atnmy-stdo-adm-v2-postgres
environment:
POSTGRES_DB: studio_admin
POSTGRES_USER: studio_admin
POSTGRES_PASSWORD: future1234!@
ports:
- "5436:5432"
volumes:
- atnmy-stdo-adm-v2-postgres:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U studio_admin -d studio_admin"]
interval: 5s
timeout: 3s
retries: 20
volumes:
atnmy-stdo-adm-v2-postgres:
+52 -8
View File
@@ -33,6 +33,9 @@ model User {
email String @unique
passwordHash String @map("password_hash")
name String?
profileImageData Bytes? @map("profile_image_data")
profileImageMime String? @map("profile_image_mime")
profileImageUpdatedAt DateTime? @map("profile_image_updated_at") @db.Timestamptz(3)
role UserRole @default(USER)
isActive Boolean @default(true) @map("is_active")
lastLoginAt DateTime? @map("last_login_at") @db.Timestamptz(3)
@@ -41,6 +44,7 @@ model User {
userLicenses UserLicense[]
refreshTokens RefreshToken[]
affiliations UserAffiliation[]
appRoles UserAppRole[]
@@map("users")
}
@@ -68,11 +72,26 @@ model License {
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
userLicenses UserLicense[]
appPermissions LicenseAppPermission[]
@@index([isActive])
@@map("licenses")
}
model LicenseAppPermission {
id String @id @default(uuid()) @db.Uuid
licenseId String @map("license_id") @db.Uuid
appKey String @map("app_key")
canLaunch Boolean @default(true) @map("can_launch")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
license License @relation(fields: [licenseId], references: [id], onDelete: Cascade)
@@unique([licenseId, appKey])
@@index([appKey])
@@map("license_app_permissions")
}
model UserLicense {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
@@ -90,17 +109,23 @@ model UserLicense {
}
model Organization {
id String @id @default(uuid()) @db.Uuid
name String
type OrganizationType @default(OTHER)
domain String?
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
affiliations UserAffiliation[]
id String @id @default(uuid()) @db.Uuid
name String
type OrganizationType @default(OTHER)
domain String?
defaultLicenseCode String? @map("default_license_code")
instructorInviteCode String @unique @map("instructor_invite_code")
isActive Boolean @default(true) @map("is_active")
createdById String? @map("created_by_id") @db.Uuid
createdByName String? @map("created_by_name")
createdByEmail String? @map("created_by_email")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
affiliations UserAffiliation[]
@@unique([name, type])
@@index([isActive, type])
@@index([createdByEmail])
@@map("organizations")
}
@@ -122,3 +147,22 @@ model UserAffiliation {
@@index([organizationId])
@@map("user_affiliations")
}
model UserAppRole {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
appKey String @map("app_key")
roleCode String @map("role_code")
assignedById String? @map("assigned_by") @db.Uuid
assignedAt DateTime @default(now()) @map("assigned_at") @db.Timestamptz(3)
startsAt DateTime? @map("starts_at") @db.Timestamptz(3)
endsAt DateTime? @map("ends_at") @db.Timestamptz(3)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, appKey, roleCode])
@@index([appKey, roleCode])
@@index([userId, appKey])
@@map("user_app_roles")
}
+65 -2
View File
@@ -19,13 +19,50 @@ model LauncherNotice {
startsAt DateTime? @map("starts_at") @db.Timestamptz(3)
endsAt DateTime? @map("ends_at") @db.Timestamptz(3)
priority Int @default(0)
createdById String? @map("created_by_id") @db.Uuid
createdByName String? @map("created_by_name")
createdByEmail String? @map("created_by_email")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
@@index([appKey, isActive, priority])
@@index([createdByEmail])
@@map("launcher_notices")
}
model LauncherTerm {
id String @id @default(uuid()) @db.Uuid
key String @unique
body String
isRequired Boolean @default(true) @map("is_required")
createdById String? @map("created_by_id") @db.Uuid
createdByName String? @map("created_by_name")
createdByEmail String? @map("created_by_email")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
@@index([createdByEmail])
@@map("launcher_terms")
}
model LauncherManualDocument {
id String @id @default(uuid()) @db.Uuid
appKey String @map("app_key")
title String
body String
isPublished Boolean @default(true) @map("is_published")
sortOrder Int @default(0) @map("sort_order")
createdById String? @map("created_by_id") @db.Uuid
createdByName String? @map("created_by_name")
createdByEmail String? @map("created_by_email")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
@@index([appKey, isPublished, sortOrder])
@@index([createdByEmail])
@@map("launcher_manual_documents")
}
model ProvisionedDevice {
id String @id @default(uuid()) @db.Uuid
accountEmail String @map("account_email")
@@ -40,6 +77,9 @@ model ProvisionedDevice {
kernel String?
distro String?
device Json?
agentVersion String? @map("agent_version")
agentUpdatedAt DateTime? @map("agent_updated_at") @db.Timestamptz(3)
agentLastSeenAt DateTime? @map("agent_last_seen_at") @db.Timestamptz(3)
installedAt DateTime @default(now()) @map("installed_at") @db.Timestamptz(3)
lastProvisionedAt DateTime @default(now()) @map("last_provisioned_at") @db.Timestamptz(3)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
@@ -48,9 +88,32 @@ model ProvisionedDevice {
@@unique([accountEmail, tailscaleHostname])
@@index([accountEmail])
@@index([tailscaleHostname])
@@index([agentLastSeenAt])
@@map("provisioned_devices")
}
model DeviceAuthorization {
id String @id @default(uuid()) @db.Uuid
deviceCodeHash String @unique @map("device_code_hash")
userCode String @unique @map("user_code")
status String @default("PENDING")
authUserId String? @map("auth_user_id") @db.Uuid
accountEmail String? @map("account_email")
authUserName String? @map("auth_user_name")
authUserRole String? @map("auth_user_role")
authUserRoles Json? @map("auth_user_roles")
device Json?
expiresAt DateTime @map("expires_at") @db.Timestamptz(3)
approvedAt DateTime? @map("approved_at") @db.Timestamptz(3)
consumedAt DateTime? @map("consumed_at") @db.Timestamptz(3)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
@@index([status, expiresAt])
@@index([authUserId, createdAt])
@@map("device_authorizations")
}
model LauncherSshKey {
id String @id @default(uuid()) @db.Uuid
accountEmail String @map("account_email")
@@ -69,7 +132,7 @@ model LauncherSshKey {
}
model LauncherCommonSetting {
model LauncherSetting {
id String @id @default(uuid()) @db.Uuid
key String @unique
name String
@@ -82,5 +145,5 @@ model LauncherCommonSetting {
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
@@index([type, isActive])
@@map("launcher_common_settings")
@@map("launcher_settings")
}
@@ -0,0 +1,8 @@
ALTER TABLE IF EXISTS "launcher_common_settings" RENAME TO "launcher_settings";
DO $$
BEGIN
IF to_regclass('public.launcher_settings') IS NOT NULL THEN
COMMENT ON TABLE "launcher_settings" IS '런처 소스, 빌드 환경 등 내부 설정';
END IF;
END $$;
@@ -0,0 +1,36 @@
CREATE TABLE IF NOT EXISTS "admin_app_permissions" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"admin_id" UUID NOT NULL,
"app_id" UUID NOT NULL,
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "admin_app_permissions_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX IF NOT EXISTS "admin_app_permissions_admin_id_app_id_key"
ON "admin_app_permissions"("admin_id", "app_id");
CREATE INDEX IF NOT EXISTS "admin_app_permissions_app_id_idx"
ON "admin_app_permissions"("app_id");
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'admin_app_permissions_admin_id_fkey'
) THEN
ALTER TABLE "admin_app_permissions"
ADD CONSTRAINT "admin_app_permissions_admin_id_fkey"
FOREIGN KEY ("admin_id") REFERENCES "admins"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'admin_app_permissions_app_id_fkey'
) THEN
ALTER TABLE "admin_app_permissions"
ADD CONSTRAINT "admin_app_permissions_app_id_fkey"
FOREIGN KEY ("app_id") REFERENCES "apps"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
@@ -0,0 +1,32 @@
DO $$
BEGIN
CREATE TYPE "AdminRole" AS ENUM ('OWNER', 'ADMIN', 'OPERATOR', 'VIEWER');
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
CREATE TABLE IF NOT EXISTS "admin_roles" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"code" "AdminRole" NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"is_active" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "admin_roles_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX IF NOT EXISTS "admin_roles_code_key"
ON "admin_roles"("code");
CREATE INDEX IF NOT EXISTS "admin_roles_is_active_sort_order_idx"
ON "admin_roles"("is_active", "sort_order");
INSERT INTO "admin_roles" ("id", "code", "name", "description", "sort_order", "is_active", "updated_at")
VALUES
(gen_random_uuid(), 'OWNER', 'OWNER', '전체 관리자 권한', 1, true, CURRENT_TIMESTAMP),
(gen_random_uuid(), 'ADMIN', 'ADMIN', '관리자 권한', 2, true, CURRENT_TIMESTAMP),
(gen_random_uuid(), 'VIEWER', 'VIEWER', '조회 권한', 3, true, CURRENT_TIMESTAMP)
ON CONFLICT ("code") DO NOTHING;
@@ -0,0 +1,5 @@
ALTER TABLE "build_jobs"
ADD COLUMN IF NOT EXISTS "release_candidate_hidden_at" TIMESTAMPTZ(3);
CREATE INDEX IF NOT EXISTS "build_jobs_release_candidate_hidden_at_idx"
ON "build_jobs"("release_candidate_hidden_at");
@@ -0,0 +1,11 @@
ALTER TABLE "launcher_build_jobs"
ADD COLUMN "requested_by_id" UUID,
ADD COLUMN "requested_by_name" TEXT,
ADD COLUMN "requested_by_email" TEXT;
CREATE INDEX IF NOT EXISTS "launcher_build_jobs_requested_by_id_idx"
ON "launcher_build_jobs"("requested_by_id");
COMMENT ON COLUMN "launcher_build_jobs"."requested_by_id" IS '빌드 작업을 요청한 관리자 ID';
COMMENT ON COLUMN "launcher_build_jobs"."requested_by_name" IS '빌드 작업을 요청한 관리자 이름';
COMMENT ON COLUMN "launcher_build_jobs"."requested_by_email" IS '빌드 작업을 요청한 관리자 이메일';
@@ -0,0 +1,41 @@
ALTER TABLE "build_jobs"
ADD COLUMN "requested_by_id" UUID,
ADD COLUMN "requested_by_name" TEXT,
ADD COLUMN "requested_by_email" TEXT;
ALTER TABLE "app_releases"
ADD COLUMN "requested_by_id" UUID,
ADD COLUMN "requested_by_name" TEXT,
ADD COLUMN "requested_by_email" TEXT;
ALTER TABLE "deployment_jobs"
ADD COLUMN "requested_by_id" UUID,
ADD COLUMN "requested_by_name" TEXT,
ADD COLUMN "requested_by_email" TEXT;
ALTER TABLE "runtime_control_jobs"
ADD COLUMN "requested_by_id" UUID,
ADD COLUMN "requested_by_name" TEXT,
ADD COLUMN "requested_by_email" TEXT;
CREATE INDEX IF NOT EXISTS "build_jobs_requested_by_id_idx"
ON "build_jobs"("requested_by_id");
CREATE INDEX IF NOT EXISTS "app_releases_requested_by_id_idx"
ON "app_releases"("requested_by_id");
CREATE INDEX IF NOT EXISTS "deployment_jobs_requested_by_id_idx"
ON "deployment_jobs"("requested_by_id");
CREATE INDEX IF NOT EXISTS "runtime_control_jobs_requested_by_id_idx"
ON "runtime_control_jobs"("requested_by_id");
COMMENT ON COLUMN "build_jobs"."requested_by_id" IS '작업을 요청한 관리자 ID';
COMMENT ON COLUMN "build_jobs"."requested_by_name" IS '작업을 요청한 관리자 이름';
COMMENT ON COLUMN "build_jobs"."requested_by_email" IS '작업을 요청한 관리자 이메일';
COMMENT ON COLUMN "app_releases"."requested_by_id" IS '작업을 요청한 관리자 ID';
COMMENT ON COLUMN "app_releases"."requested_by_name" IS '작업을 요청한 관리자 이름';
COMMENT ON COLUMN "app_releases"."requested_by_email" IS '작업을 요청한 관리자 이메일';
COMMENT ON COLUMN "deployment_jobs"."requested_by_id" IS '작업을 요청한 관리자 ID';
COMMENT ON COLUMN "deployment_jobs"."requested_by_name" IS '작업을 요청한 관리자 이름';
COMMENT ON COLUMN "deployment_jobs"."requested_by_email" IS '작업을 요청한 관리자 이메일';
COMMENT ON COLUMN "runtime_control_jobs"."requested_by_id" IS '작업을 요청한 관리자 ID';
COMMENT ON COLUMN "runtime_control_jobs"."requested_by_name" IS '작업을 요청한 관리자 이름';
COMMENT ON COLUMN "runtime_control_jobs"."requested_by_email" IS '작업을 요청한 관리자 이메일';
@@ -0,0 +1,16 @@
DO $$
BEGIN
IF to_regclass('public.launcher_notices') IS NOT NULL THEN
ALTER TABLE "launcher_notices"
ADD COLUMN IF NOT EXISTS "created_by_id" UUID,
ADD COLUMN IF NOT EXISTS "created_by_name" TEXT,
ADD COLUMN IF NOT EXISTS "created_by_email" TEXT;
END IF;
END $$;
DO $$
BEGIN
IF to_regclass('public.launcher_notices') IS NOT NULL THEN
CREATE INDEX IF NOT EXISTS "launcher_notices_created_by_email_idx" ON "launcher_notices"("created_by_email");
END IF;
END $$;
@@ -0,0 +1,7 @@
DO $$
BEGIN
IF to_regclass('public.organizations') IS NOT NULL THEN
ALTER TABLE "organizations"
ADD COLUMN IF NOT EXISTS "default_license_code" TEXT;
END IF;
END $$;
@@ -0,0 +1,19 @@
DO $$
BEGIN
IF to_regclass('public.licenses') IS NOT NULL THEN
CREATE TABLE IF NOT EXISTS "license_app_permissions" (
"id" UUID NOT NULL,
"license_id" UUID NOT NULL,
"app_key" TEXT NOT NULL,
"can_launch" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "license_app_permissions_pkey" PRIMARY KEY ("id"),
CONSTRAINT "license_app_permissions_license_id_fkey" FOREIGN KEY ("license_id") REFERENCES "licenses"("id") ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE UNIQUE INDEX IF NOT EXISTS "license_app_permissions_license_id_app_key_key" ON "license_app_permissions"("license_id", "app_key");
CREATE INDEX IF NOT EXISTS "license_app_permissions_app_key_idx" ON "license_app_permissions"("app_key");
END IF;
END $$;
@@ -0,0 +1,28 @@
DO $$
BEGIN
IF to_regclass('public.launcher_notices') IS NOT NULL THEN
CREATE TABLE IF NOT EXISTS "launcher_terms" (
"id" UUID NOT NULL,
"key" TEXT NOT NULL,
"body" TEXT NOT NULL,
"is_required" BOOLEAN NOT NULL DEFAULT true,
"created_by_id" UUID,
"created_by_name" TEXT,
"created_by_email" TEXT,
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "launcher_terms_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX IF NOT EXISTS "launcher_terms_key_key" ON "launcher_terms"("key");
CREATE INDEX IF NOT EXISTS "launcher_terms_created_by_email_idx" ON "launcher_terms"("created_by_email");
ALTER TABLE "launcher_terms" DROP COLUMN IF EXISTS "title";
ALTER TABLE "launcher_terms" DROP COLUMN IF EXISTS "priority";
ALTER TABLE "launcher_terms" DROP COLUMN IF EXISTS "version";
ALTER TABLE "launcher_terms" DROP COLUMN IF EXISTS "is_active";
ALTER TABLE "launcher_terms" DROP COLUMN IF EXISTS "starts_at";
ALTER TABLE "launcher_terms" DROP COLUMN IF EXISTS "ends_at";
END IF;
END $$;
@@ -0,0 +1,15 @@
CREATE TABLE "launcher_manual_documents" (
"id" UUID NOT NULL,
"key" TEXT NOT NULL,
"title" TEXT NOT NULL,
"body" TEXT NOT NULL,
"is_published" BOOLEAN NOT NULL DEFAULT true,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(3) NOT NULL,
CONSTRAINT "launcher_manual_documents_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "launcher_manual_documents_key_key" ON "launcher_manual_documents"("key");
CREATE INDEX "launcher_manual_documents_is_published_sort_order_idx" ON "launcher_manual_documents"("is_published", "sort_order");
@@ -0,0 +1,6 @@
ALTER TABLE "launcher_manual_documents"
ADD COLUMN "app_key" TEXT NOT NULL DEFAULT 'Launcher';
DROP INDEX IF EXISTS "launcher_manual_documents_is_published_sort_order_idx";
CREATE INDEX "launcher_manual_documents_app_key_is_published_sort_order_idx"
ON "launcher_manual_documents"("app_key", "is_published", "sort_order");
@@ -0,0 +1,2 @@
DROP INDEX IF EXISTS "launcher_manual_documents_key_key";
ALTER TABLE "launcher_manual_documents" DROP COLUMN IF EXISTS "key";
@@ -0,0 +1,7 @@
ALTER TABLE "launcher_manual_documents"
ADD COLUMN IF NOT EXISTS "created_by_id" UUID,
ADD COLUMN IF NOT EXISTS "created_by_name" TEXT,
ADD COLUMN IF NOT EXISTS "created_by_email" TEXT;
CREATE INDEX IF NOT EXISTS "launcher_manual_documents_created_by_email_idx"
ON "launcher_manual_documents"("created_by_email");
@@ -0,0 +1,9 @@
DO $$
BEGIN
IF to_regclass('public.organizations') IS NOT NULL THEN
ALTER TABLE "organizations"
ADD COLUMN IF NOT EXISTS "created_by_id" UUID,
ADD COLUMN IF NOT EXISTS "created_by_name" TEXT,
ADD COLUMN IF NOT EXISTS "created_by_email" TEXT;
END IF;
END $$;
@@ -0,0 +1,2 @@
ALTER TABLE "build_jobs"
ADD COLUMN "artifact_sha256" TEXT;
+33
View File
@@ -74,11 +74,25 @@ model Admin {
sessions AdminSession[]
auditLogs AdminAuditLog[]
sshKeys AdminSshKey[]
appPermissions AdminAppPermission[]
@@index([isActive, role])
@@map("admins")
}
model AdminAppPermission {
id String @id @default(uuid()) @db.Uuid
adminId String @map("admin_id") @db.Uuid
appId String @map("app_id") @db.Uuid
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
admin Admin @relation(fields: [adminId], references: [id], onDelete: Cascade)
app App @relation(fields: [appId], references: [id], onDelete: Cascade)
@@unique([adminId, appId])
@@index([appId])
@@map("admin_app_permissions")
}
model AdminSshKey {
id String @id @default(uuid()) @db.Uuid
adminId String @map("admin_id") @db.Uuid
@@ -142,6 +156,7 @@ model App {
releases AppRelease[]
deployments DeploymentJob[]
runtimeProfiles RuntimeEnvProfile[]
adminPermissions AdminAppPermission[]
@@map("apps")
}
@@ -200,15 +215,21 @@ model BuildJob {
branch String
commitSha String? @map("commit_sha")
artifact String?
artifactSha256 String? @map("artifact_sha256")
image String?
status JobStatus @default(PENDING)
requestedById String? @map("requested_by_id") @db.Uuid
requestedByName String? @map("requested_by_name")
requestedByEmail String? @map("requested_by_email")
logs String?
startedAt DateTime? @map("started_at") @db.Timestamptz(3)
finishedAt DateTime? @map("finished_at") @db.Timestamptz(3)
releaseCandidateHiddenAt DateTime? @map("release_candidate_hidden_at") @db.Timestamptz(3)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
component AppComponent @relation(fields: [componentId], references: [id], onDelete: Cascade)
@@index([componentId, createdAt])
@@index([releaseCandidateHiddenAt])
@@map("build_jobs")
}
@@ -221,6 +242,9 @@ model AppRelease {
artifact String?
imageTags Json? @map("image_tags")
status JobStatus @default(PENDING)
requestedById String? @map("requested_by_id") @db.Uuid
requestedByName String? @map("requested_by_name")
requestedByEmail String? @map("requested_by_email")
logs String?
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
@@ -239,6 +263,9 @@ model DeploymentJob {
namespace String
status JobStatus @default(PENDING)
requestedBy String? @map("requested_by")
requestedById String? @map("requested_by_id") @db.Uuid
requestedByName String? @map("requested_by_name")
requestedByEmail String? @map("requested_by_email")
logs String?
startedAt DateTime? @map("started_at") @db.Timestamptz(3)
finishedAt DateTime? @map("finished_at") @db.Timestamptz(3)
@@ -261,6 +288,9 @@ model LauncherBuildJob {
uploadPath String @map("upload_path")
downloadUrl String @map("download_url")
status JobStatus @default(PENDING)
requestedById String? @map("requested_by_id") @db.Uuid
requestedByName String? @map("requested_by_name")
requestedByEmail String? @map("requested_by_email")
logs String?
startedAt DateTime? @map("started_at") @db.Timestamptz(3)
finishedAt DateTime? @map("finished_at") @db.Timestamptz(3)
@@ -282,6 +312,9 @@ model RuntimeControlJob {
action RuntimeAction
status JobStatus @default(PENDING)
requestedBy String? @map("requested_by")
requestedById String? @map("requested_by_id") @db.Uuid
requestedByName String? @map("requested_by_name")
requestedByEmail String? @map("requested_by_email")
request Json?
result Json?
logs String?
+40 -2
View File
@@ -1,13 +1,39 @@
import { Body, Controller, Post, Req } from '@nestjs/common';
import { Body, Controller, Delete, Get, Patch, Post, Put, Req } from '@nestjs/common';
import type { Request } from 'express';
import { AdminAuthService } from './admin-auth.service';
import { Public } from './decorators/admin-auth.decorators';
import { AdminLoginDto } from './dto';
import { AdminLoginDto, ChangeAdminPasswordDto } from './dto';
@Controller('auth/admin')
export class AdminAuthController {
constructor(private readonly adminAuthService: AdminAuthService) {}
@Get('me')
me(@Req() request: { admin?: { id: string } }) {
return this.adminAuthService.me(request.admin!.id);
}
@Put('avatar')
async updateAvatar(@Req() request: Request & { admin?: { id: string } }) {
const mimeType = String(request.headers['content-type'] ?? '').split(';')[0].trim().toLowerCase();
const imageData = await readRequestBody(request);
return this.adminAuthService.updateAvatar(request.admin!.id, imageData, mimeType);
}
@Delete('avatar')
deleteAvatar(@Req() request: { admin?: { id: string } }) {
return this.adminAuthService.deleteAvatar(request.admin!.id);
}
@Patch('password')
changePassword(
@Req() request: { admin?: { id: string } },
@Body() body: ChangeAdminPasswordDto,
) {
return this.adminAuthService.changePassword(request.admin!.id, body);
}
@Public()
@Post('login')
login(
@@ -22,3 +48,15 @@ export class AdminAuthController {
});
}
}
function readRequestBody(request: Request) {
return new Promise<Buffer>((resolve, reject) => {
const chunks: Buffer[] = [];
request.on('data', (chunk: Buffer | string) => {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
});
request.on('end', () => resolve(Buffer.concat(chunks)));
request.on('error', reject);
});
}
+2 -1
View File
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { GiteaModule } from '../gitea/gitea.module';
import { PrismaModule } from '../prisma/prisma.module';
import { AdminAuthController } from './admin-auth.controller';
import { AdminAuthService } from './admin-auth.service';
@Module({
imports: [PrismaModule],
imports: [GiteaModule, PrismaModule],
controllers: [AdminAuthController],
providers: [AdminAuthService],
})
+271 -3
View File
@@ -1,13 +1,22 @@
import { randomBytes, scryptSync, timingSafeEqual, createHash } from 'node:crypto';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { DeleteObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { BadRequestException, Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { GiteaService } from '../gitea/gitea.service';
import { PrismaService } from '../prisma/prisma.service';
import { AdminLoginDto } from './dto';
import { AdminLoginDto, ChangeAdminPasswordDto } from './dto';
@Injectable()
export class AdminAuthService {
constructor(private readonly prisma: PrismaService) {}
private r2Client?: S3Client;
constructor(
private readonly configService: ConfigService,
private readonly giteaService: GiteaService,
private readonly prisma: PrismaService,
) {}
async login(
body: AdminLoginDto,
@@ -22,6 +31,19 @@ export class AdminAuthService {
role: true,
isActive: true,
passwordHash: true,
appPermissions: {
select: { appId: true },
},
sshKeys: {
orderBy: { createdAt: 'desc' },
select: {
id: true,
label: true,
fingerprint: true,
giteaUsername: true,
createdAt: true,
},
},
},
});
@@ -49,6 +71,8 @@ export class AdminAuthService {
}),
]);
const avatarUrl = this.getAdminAvatarUrl(admin.id);
return {
token,
expiresAt,
@@ -57,9 +81,235 @@ export class AdminAuthService {
email: admin.email,
name: admin.name,
role: admin.role,
avatarUrl,
appPermissions: admin.appPermissions.map((permission) => permission.appId),
sshKeys: admin.sshKeys,
},
};
}
async me(adminId: string) {
const admin = await this.prisma.admin.findUnique({
where: { id: adminId },
select: {
id: true,
email: true,
name: true,
role: true,
appPermissions: {
select: { appId: true },
},
sshKeys: {
orderBy: { createdAt: 'desc' },
select: {
id: true,
label: true,
fingerprint: true,
giteaUsername: true,
createdAt: true,
},
},
},
});
if (!admin) {
throw new UnauthorizedException('관리자 세션이 유효하지 않습니다.');
}
return {
admin: {
id: admin.id,
email: admin.email,
name: admin.name,
role: admin.role,
avatarUrl: this.getAdminAvatarUrl(admin.id),
appPermissions: admin.appPermissions.map((permission) => permission.appId),
sshKeys: admin.sshKeys,
},
};
}
async updateAvatar(adminId: string, imageData: Buffer, mimeType: string) {
if (!['image/jpeg', 'image/png', 'image/webp'].includes(mimeType)) {
throw new BadRequestException('지원하지 않는 이미지 형식입니다.');
}
if (!imageData.length) {
throw new BadRequestException('이미지 파일이 비어 있습니다.');
}
if (imageData.length > 80 * 1024) {
throw new BadRequestException('프로필 이미지는 80KB 이하로 업로드해 주세요.');
}
const admin = await this.getAdminForAvatar(adminId);
const giteaUsername = await this.ensureGiteaUser(admin);
await this.uploadAvatarToR2(admin.id, imageData, mimeType);
await this.giteaService.updateUserAvatar(giteaUsername, imageData, mimeType);
return {
avatarUrl: this.getAdminAvatarUrl(admin.id),
};
}
async deleteAvatar(adminId: string) {
const admin = await this.getAdminForAvatar(adminId);
const giteaUsername = this.resolveGiteaUsername(admin);
if (this.isR2Enabled()) {
await this.deleteAvatarFromR2(admin.id);
}
await this.giteaService.deleteUserAvatar(giteaUsername);
return {
avatarUrl: this.getAdminAvatarUrl(admin.id),
};
}
async changePassword(adminId: string, body: ChangeAdminPasswordDto) {
if (body.currentPassword === body.newPassword) {
throw new BadRequestException('새 비밀번호는 현재 비밀번호와 달라야 합니다.');
}
const admin = await this.prisma.admin.findUnique({
where: { id: adminId },
select: {
id: true,
passwordHash: true,
isActive: true,
},
});
if (!admin || !admin.isActive) {
throw new UnauthorizedException('관리자 세션이 유효하지 않습니다.');
}
if (!verifyPassword(body.currentPassword, admin.passwordHash)) {
throw new BadRequestException('현재 비밀번호가 올바르지 않습니다.');
}
await this.prisma.admin.update({
where: { id: admin.id },
data: { passwordHash: hashPassword(body.newPassword) },
});
return { ok: true };
}
private getAdminAvatarUrl(adminId: string) {
const publicBaseUrl = this.configService.get<string>('R2_PUBLIC_BASE_URL')?.replace(/\/+$/, '');
if (!publicBaseUrl) return null;
return `${publicBaseUrl}/${this.createAvatarObjectKey(adminId)}`;
}
private async getAdminForAvatar(adminId: string) {
const admin = await this.prisma.admin.findUnique({
where: { id: adminId },
select: {
id: true,
email: true,
name: true,
sshKeys: {
where: { giteaUsername: { not: null } },
orderBy: { createdAt: 'desc' },
take: 1,
select: { giteaUsername: true },
},
},
});
if (!admin) {
throw new UnauthorizedException('관리자 세션이 유효하지 않습니다.');
}
return admin;
}
private async ensureGiteaUser(admin: { email: string; name: string; sshKeys?: Array<{ giteaUsername: string | null }> }) {
const storedUsername = this.resolveStoredGiteaUsername(admin);
if (storedUsername) return storedUsername;
const username = createGiteaUsername(admin.email);
await this.giteaService.createUser({
username,
email: admin.email,
password: randomBytes(24).toString('hex'),
fullName: admin.name,
});
return username;
}
private resolveGiteaUsername(admin: { email: string; sshKeys?: Array<{ giteaUsername: string | null }> }) {
return this.resolveStoredGiteaUsername(admin) ?? createGiteaUsername(admin.email);
}
private resolveStoredGiteaUsername(admin: { sshKeys?: Array<{ giteaUsername: string | null }> }) {
return admin.sshKeys?.find((key) => key.giteaUsername?.trim())?.giteaUsername?.trim() || null;
}
private async uploadAvatarToR2(adminId: string, imageData: Buffer, mimeType: string) {
const bucket = this.configService.get<string>('R2_BUCKET');
const accessKeyId = this.configService.get<string>('R2_ACCESS_KEY_ID');
const secretAccessKey = this.configService.get<string>('R2_SECRET_ACCESS_KEY');
if (!bucket || !accessKeyId || !secretAccessKey) {
throw new BadRequestException('R2 설정이 필요합니다.');
}
await this.getR2Client(accessKeyId, secretAccessKey).send(new PutObjectCommand({
Bucket: bucket,
Key: this.createAvatarObjectKey(adminId),
Body: imageData,
ContentType: mimeType,
CacheControl: 'public, max-age=60',
}));
}
private async deleteAvatarFromR2(adminId: string) {
const bucket = this.configService.get<string>('R2_BUCKET');
const accessKeyId = this.configService.get<string>('R2_ACCESS_KEY_ID');
const secretAccessKey = this.configService.get<string>('R2_SECRET_ACCESS_KEY');
if (!bucket || !accessKeyId || !secretAccessKey) return;
await this.getR2Client(accessKeyId, secretAccessKey).send(new DeleteObjectCommand({
Bucket: bucket,
Key: this.createAvatarObjectKey(adminId),
}));
}
private getR2Client(accessKeyId: string, secretAccessKey: string) {
if (this.r2Client) return this.r2Client;
const endpoint = this.configService.get<string>('R2_ENDPOINT')
|| `https://${this.configService.get<string>('R2_ACCOUNT_ID')}.r2.cloudflarestorage.com`;
this.r2Client = new S3Client({
region: this.configService.get<string>('R2_REGION', 'auto'),
endpoint,
credentials: {
accessKeyId,
secretAccessKey,
},
forcePathStyle: this.configService.get<string>('R2_FORCE_PATH_STYLE', 'true') !== 'false',
});
return this.r2Client;
}
private isR2Enabled() {
return Boolean(
this.configService.get<string>('R2_BUCKET')
&& this.configService.get<string>('R2_ACCESS_KEY_ID')
&& this.configService.get<string>('R2_SECRET_ACCESS_KEY'),
);
}
private createAvatarObjectKey(adminId: string) {
const prefix = this.configService.get<string>('R2_PUBLIC_FILE_PREFIX', 'studio-files/public');
return `${prefix.replace(/^\/+|\/+$/g, '')}/admins/${adminId}/avatar`;
}
}
function verifyPassword(password: string, passwordHash: string) {
@@ -81,3 +331,21 @@ function verifyPassword(password: string, passwordHash: string) {
function hashToken(token: string) {
return createHash('sha256').update(token).digest('hex');
}
function hashPassword(password: string) {
const salt = randomBytes(16).toString('hex');
const hash = scryptSync(password, salt, 64).toString('hex');
return `scrypt:${salt}:${hash}`;
}
function createGiteaUsername(email: string) {
const account = email.split('@')[0] || email;
const username = account
.trim()
.toLowerCase()
.replace(/[^a-z0-9._-]+/g, '-')
.replace(/^[._-]+|[._-]+$/g, '')
.slice(0, 40);
return username || `admin-${createHash('sha1').update(email).digest('hex').slice(0, 8)}`;
}
+10 -1
View File
@@ -1,4 +1,4 @@
import { IsEmail, IsString } from 'class-validator';
import { IsEmail, IsString, MinLength } from 'class-validator';
export class AdminLoginDto {
@IsEmail()
@@ -7,3 +7,12 @@ export class AdminLoginDto {
@IsString()
password!: string;
}
export class ChangeAdminPasswordDto {
@IsString()
currentPassword!: string;
@IsString()
@MinLength(8)
newPassword!: string;
}
+9
View File
@@ -0,0 +1,9 @@
import { ConfigService } from '@nestjs/config';
export function getAdminAvatarUrl(adminId: string | null | undefined, configService: ConfigService) {
const publicBaseUrl = configService.get<string>('R2_PUBLIC_BASE_URL')?.replace(/\/+$/, '');
if (!adminId || !publicBaseUrl) return null;
const prefix = configService.get<string>('R2_PUBLIC_FILE_PREFIX', 'studio-files/public');
return `${publicBaseUrl}/${prefix.replace(/^\/+|\/+$/g, '')}/admins/${adminId}/avatar`;
}
+15 -3
View File
@@ -1,9 +1,11 @@
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common';
import {
CreateAdminSshKeyDto,
CreateAdminDto,
ListAdminsQuery,
ResetAdminPasswordDto,
UpdateAdminAppPermissionsDto,
UpdateAdminRoleDto,
UpdateAdminStatusDto,
} from './dto';
@@ -22,8 +24,8 @@ export class AdminsController {
}
@Get()
list() {
return this.adminsService.list();
list(@Query() query: ListAdminsQuery) {
return this.adminsService.list(query);
}
@Post()
@@ -46,6 +48,16 @@ export class AdminsController {
return this.adminsService.resetPassword(id, body);
}
@Get(':id/app-permissions')
listAppPermissions(@Param('id') id: string) {
return this.adminsService.listAppPermissions(id);
}
@Put(':id/app-permissions')
updateAppPermissions(@Param('id') id: string, @Body() body: UpdateAdminAppPermissionsDto) {
return this.adminsService.updateAppPermissions(id, body);
}
@Post(':id/ssh-keys')
createSshKey(@Param('id') id: string, @Body() body: CreateAdminSshKeyDto) {
return this.adminsService.createSshKey(id, body);
+140 -16
View File
@@ -1,14 +1,18 @@
import { createHash, randomBytes, scryptSync } from 'node:crypto';
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { GiteaService } from '../gitea/gitea.service';
import { PrismaService } from '../prisma/prisma.service';
import { getAdminAvatarUrl } from './admin-avatar';
import { AdminRole } from './admin-role';
import {
CreateAdminSshKeyDto,
CreateAdminDto,
ListAdminsQuery,
ResetAdminPasswordDto,
UpdateAdminAppPermissionsDto,
UpdateAdminRoleDto,
UpdateAdminStatusDto,
} from './dto';
@@ -16,6 +20,7 @@ import {
@Injectable()
export class AdminsService {
constructor(
private readonly configService: ConfigService,
private readonly giteaService: GiteaService,
private readonly prisma: PrismaService,
) {}
@@ -37,9 +42,29 @@ export class AdminsService {
});
}
list() {
return this.prisma.admin.findMany({
async list(query: ListAdminsQuery = {}) {
const page = toPositiveInt(query.page, 1);
const limit = Math.min(toPositiveInt(query.limit, 20), 100);
const keyword = query.keyword?.trim();
const where = {
...(query.role ? { role: query.role as AdminRole } : {}),
...(query.state === 'active' ? { isActive: true } : query.state === 'inactive' ? { isActive: false } : {}),
...(keyword
? {
OR: [
{ email: { contains: keyword, mode: 'insensitive' as const } },
{ name: { contains: keyword, mode: 'insensitive' as const } },
],
}
: {}),
};
const [admins, total] = await this.prisma.$transaction([
this.prisma.admin.findMany({
where,
orderBy: [{ role: 'asc' }, { createdAt: 'desc' }],
skip: (page - 1) * limit,
take: limit,
select: {
id: true,
email: true,
@@ -63,17 +88,44 @@ export class AdminsService {
orderBy: { createdAt: 'desc' },
select: adminSshKeySelect,
},
appPermissions: {
select: { appId: true },
},
},
});
}),
this.prisma.admin.count({ where }),
]);
return {
admins: admins.map((admin, index) => {
return {
...admin,
no: total - ((page - 1) * limit + index),
avatarUrl: getAdminAvatarUrl(admin.id, this.configService),
};
}),
total,
page,
limit,
};
}
async create(body: CreateAdminDto) {
await this.ensureRoleEnabled(body.role);
const email = body.email.trim().toLowerCase();
const name = body.name.trim();
await this.giteaService.createUser({
username: createGiteaUsername(email),
email,
password: body.temporaryPassword,
fullName: name,
});
const admin = await this.prisma.admin.create({
data: {
email: body.email.trim().toLowerCase(),
name: body.name.trim(),
email,
name,
role: body.role,
passwordHash: hashPassword(body.temporaryPassword),
},
@@ -84,7 +136,12 @@ export class AdminsService {
}
async updateStatus(id: string, body: UpdateAdminStatusDto) {
await this.ensureExists(id);
const existing = await this.ensureExists(id);
await this.giteaService.updateUserLoginDisabled(
createGiteaUsername(existing.email),
!body.isActive,
);
const admin = await this.prisma.admin.update({
where: { id },
@@ -132,21 +189,68 @@ export class AdminsService {
return { admin };
}
async listAppPermissions(id: string) {
await this.ensureExists(id);
const permissions = await this.prisma.adminAppPermission.findMany({
where: { adminId: id },
orderBy: { createdAt: 'asc' },
select: { appId: true },
});
return { appIds: permissions.map((permission) => permission.appId) };
}
async updateAppPermissions(id: string, body: UpdateAdminAppPermissionsDto) {
const admin = await this.prisma.admin.findUnique({
where: { id },
select: { id: true, role: true },
});
if (!admin) {
throw new NotFoundException('관리자를 찾을 수 없습니다.');
}
if (admin.role === AdminRole.OWNER) {
throw new BadRequestException('오너 권한은 앱 권한을 개별 설정하지 않습니다.');
}
const appIds = [...new Set(body.appIds.filter(Boolean))];
const existingApps = await this.prisma.app.findMany({
where: { id: { in: appIds } },
select: { id: true },
});
const existingAppIds = new Set(existingApps.map((app) => app.id));
const validAppIds = appIds.filter((appId) => existingAppIds.has(appId));
await this.prisma.$transaction([
this.prisma.adminAppPermission.deleteMany({ where: { adminId: id } }),
...(validAppIds.length
? [
this.prisma.adminAppPermission.createMany({
data: validAppIds.map((appId) => ({ adminId: id, appId })),
skipDuplicates: true,
}),
]
: []),
]);
return { appIds: validAppIds };
}
async createSshKey(adminId: string, body: CreateAdminSshKeyDto) {
await this.ensureExists(adminId);
const admin = await this.ensureExists(adminId);
const parsedKey = parseSshPublicKey(body.publicKey);
const label = body.label?.trim() || `${parsedKey.keyType} ${parsedKey.fingerprint}`;
const giteaUsername = body.giteaUsername?.trim() || null;
const giteaUsername = createGiteaUsername(admin.email);
let giteaKeyId: number | null = null;
if (giteaUsername) {
const giteaKey = await this.giteaService.createUserSshKey(giteaUsername, {
title: label,
key: parsedKey.normalizedKey,
});
giteaKeyId = giteaKey.id;
}
const giteaKey = await this.giteaService.createUserSshKey(giteaUsername, {
title: label,
key: parsedKey.normalizedKey,
});
giteaKeyId = giteaKey.id;
const sshKey = await this.prisma.adminSshKey.create({
data: {
@@ -220,12 +324,14 @@ export class AdminsService {
private async ensureExists(id: string) {
const admin = await this.prisma.admin.findUnique({
where: { id },
select: { id: true },
select: { id: true, email: true },
});
if (!admin) {
throw new NotFoundException('관리자를 찾을 수 없습니다.');
}
return admin;
}
}
@@ -256,6 +362,24 @@ function hashPassword(password: string) {
return `scrypt:${salt}:${hash}`;
}
function toPositiveInt(value: string | number | undefined, fallback: number) {
const parsed = Number(value);
if (!Number.isFinite(parsed) || parsed < 1) return fallback;
return Math.floor(parsed);
}
function createGiteaUsername(email: string) {
const account = email.split('@')[0] || email;
const username = account
.trim()
.toLowerCase()
.replace(/[^a-z0-9._-]+/g, '-')
.replace(/^[._-]+|[._-]+$/g, '')
.slice(0, 40);
return username || `admin-${createHash('sha1').update(email).digest('hex').slice(0, 8)}`;
}
function parseSshPublicKey(publicKey: string) {
const normalizedKey = publicKey.trim().replace(/\s+/g, ' ');
const [keyType, keyBody] = normalizedKey.split(' ');
+31 -1
View File
@@ -1,7 +1,31 @@
import { IsBoolean, IsEmail, IsEnum, IsOptional, IsString, MinLength } from 'class-validator';
import { Transform } from 'class-transformer';
import { IsArray, IsBoolean, IsEmail, IsEnum, IsOptional, IsString, IsUUID, MinLength } from 'class-validator';
import { AdminRole } from './admin-role';
export class ListAdminsQuery {
@IsOptional()
@IsString()
page?: string;
@IsOptional()
@IsString()
limit?: string;
@IsOptional()
@IsString()
keyword?: string;
@IsOptional()
@IsString()
role?: string;
@IsOptional()
@IsString()
state?: string;
}
export class CreateAdminDto {
@Transform(({ value }) => typeof value === 'string' ? value.trim().toLowerCase() : value)
@IsEmail()
email!: string;
@@ -44,3 +68,9 @@ export class CreateAdminSshKeyDto {
@IsString()
giteaUsername?: string;
}
export class UpdateAdminAppPermissionsDto {
@IsArray()
@IsUUID('4', { each: true })
appIds!: string[];
}
+23 -1
View File
@@ -1,7 +1,9 @@
import { Controller, Delete, Get, Param, Patch, Query, Body } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common';
import {
ListLauncherSshKeysQuery,
CreateDeviceAuthorizationDto,
ListDeviceAuthorizationUsersQuery,
ListProvisionedDevicesQuery,
UpdateProvisionedDeviceDto,
} from './dto';
@@ -38,4 +40,24 @@ export class AgentsController {
removeSshKey(@Param('id') id: string) {
return this.agentsService.removeSshKey(id);
}
@Get('registration-users')
listRegistrationUsers(@Query() query: ListDeviceAuthorizationUsersQuery) {
return this.agentsService.listRegistrationUsers(query);
}
@Get('device-authorizations')
listDeviceAuthorizations() {
return this.agentsService.listDeviceAuthorizations();
}
@Post('device-authorizations')
createDeviceAuthorization(@Body() body: CreateDeviceAuthorizationDto) {
return this.agentsService.createDeviceAuthorization(body.userId);
}
@Post('device-authorizations/:id/cancel')
cancelDeviceAuthorization(@Param('id') id: string) {
return this.agentsService.cancelDeviceAuthorization(id);
}
}
+2 -1
View File
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { AuthPrismaModule } from '../auth-prisma/auth-prisma.module';
import { LauncherPrismaModule } from '../launcher-prisma/launcher-prisma.module';
import { AgentsController } from './agents.controller';
import { AgentsService } from './agents.service';
@Module({
imports: [LauncherPrismaModule],
imports: [AuthPrismaModule, LauncherPrismaModule],
controllers: [AgentsController],
providers: [AgentsService],
})
+115 -1
View File
@@ -1,7 +1,10 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { createHash, randomBytes } from 'node:crypto';
import { AuthPrismaService } from '../auth-prisma/auth-prisma.service';
import { LauncherPrismaService } from '../launcher-prisma/launcher-prisma.service';
import {
ListDeviceAuthorizationUsersQuery,
ListLauncherSshKeysQuery,
ListProvisionedDevicesQuery,
UpdateProvisionedDeviceDto,
@@ -9,7 +12,10 @@ import {
@Injectable()
export class AgentsService {
constructor(private readonly launcherPrisma: LauncherPrismaService) {}
constructor(
private readonly authPrisma: AuthPrismaService,
private readonly launcherPrisma: LauncherPrismaService,
) {}
async listDevices(query: ListProvisionedDevicesQuery) {
const keyword = query.keyword?.trim();
@@ -109,6 +115,114 @@ export class AgentsService {
return { ok: true };
}
async listRegistrationUsers(query: ListDeviceAuthorizationUsersQuery) {
const keyword = query.keyword?.trim();
const users = await this.authPrisma.user.findMany({
where: {
role: 'USER',
isActive: true,
...(keyword
? {
OR: [
{ email: { contains: keyword, mode: 'insensitive' } },
{ name: { contains: keyword, mode: 'insensitive' } },
],
}
: {}),
},
orderBy: [{ name: 'asc' }, { email: 'asc' }],
take: 20,
select: { id: true, name: true, email: true },
});
return { users };
}
async listDeviceAuthorizations() {
const now = new Date();
const authorizations = await this.launcherPrisma.deviceAuthorization.findMany({
where: {
OR: [
{ status: { in: ['APPROVED', 'CONSUMING'] } },
{ createdAt: { gte: new Date(now.getTime() - 24 * 60 * 60 * 1000) } },
],
},
orderBy: { createdAt: 'desc' },
take: 50,
select: {
id: true,
userCode: true,
status: true,
authUserId: true,
accountEmail: true,
authUserName: true,
expiresAt: true,
createdAt: true,
},
});
return {
authorizations: authorizations.map((authorization) => ({
...authorization,
status: authorization.status === 'APPROVED' && authorization.expiresAt <= now ? 'EXPIRED' : authorization.status,
})),
};
}
async createDeviceAuthorization(userId: string) {
const user = await this.authPrisma.user.findFirst({
where: { id: userId, role: 'USER', isActive: true },
select: { id: true, email: true, name: true, role: true },
});
if (!user) {
throw new NotFoundException('플랫폼 등록 대상 사용자를 찾을 수 없습니다.');
}
const now = new Date();
const expiresAt = new Date(now.getTime() + 5 * 60 * 1000);
await this.launcherPrisma.deviceAuthorization.updateMany({
where: {
authUserId: user.id,
accountEmail: user.email,
status: 'APPROVED',
expiresAt: { gt: now },
},
data: { status: 'CANCELLED' },
});
const authorization = await this.launcherPrisma.deviceAuthorization.create({
data: {
deviceCodeHash: createHash('sha256').update(randomBytes(32).toString('base64url')).digest('hex'),
userCode: String(randomBytes(4).readUInt32BE() % 1_000_000).padStart(6, '0'),
status: 'APPROVED',
authUserId: user.id,
accountEmail: user.email,
authUserName: user.name,
authUserRole: user.role,
authUserRoles: [user.role],
expiresAt,
approvedAt: now,
},
select: { id: true, userCode: true, expiresAt: true, accountEmail: true, authUserName: true, status: true },
});
return { authorization };
}
async cancelDeviceAuthorization(id: string) {
const result = await this.launcherPrisma.deviceAuthorization.updateMany({
where: { id, status: { in: ['APPROVED', 'CONSUMING'] } },
data: { status: 'CANCELLED' },
});
if (!result.count) {
throw new NotFoundException('취소할 플랫폼 등록 키를 찾을 수 없습니다.');
}
return { ok: true };
}
private async ensureDeviceExists(id: string) {
const device = await this.launcherPrisma.provisionedDevice.findUnique({
where: { id },
+12 -1
View File
@@ -1,4 +1,4 @@
import { IsOptional, IsString } from 'class-validator';
import { IsOptional, IsString, IsUUID } from 'class-validator';
export class ListProvisionedDevicesQuery {
@IsOptional()
@@ -37,3 +37,14 @@ export class ListLauncherSshKeysQuery {
@IsString()
keyword?: string;
}
export class ListDeviceAuthorizationUsersQuery {
@IsOptional()
@IsString()
keyword?: string;
}
export class CreateDeviceAuthorizationDto {
@IsUUID()
userId!: string;
}
+6
View File
@@ -11,11 +11,14 @@ import { GiteaModule } from './gitea/gitea.module';
import { HealthModule } from './health/health.module';
import { LauncherModule } from './launcher/launcher.module';
import { LicensesModule } from './licenses/licenses.module';
import { ManualsModule } from './manuals/manuals.module';
import { NoticesModule } from './notices/notices.module';
import { OrganizationsModule } from './organizations/organizations.module';
import { PageAgentModule } from './page-agent/page-agent.module';
import { PrismaModule } from './prisma/prisma.module';
import { RuntimeModule } from './runtime/runtime.module';
import { TailscaleModule } from './tailscale/tailscale.module';
import { TermsModule } from './terms/terms.module';
import { UsersModule } from './users/users.module';
import { WoodpeckerModule } from './woodpecker/woodpecker.module';
@@ -38,11 +41,14 @@ import { WoodpeckerModule } from './woodpecker/woodpecker.module';
HealthModule,
LauncherModule,
LicensesModule,
ManualsModule,
NoticesModule,
OrganizationsModule,
PageAgentModule,
PrismaModule,
RuntimeModule,
TailscaleModule,
TermsModule,
UsersModule,
WoodpeckerModule,
],
+47 -10
View File
@@ -1,4 +1,4 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, Req } from '@nestjs/common';
import { LinkComponentSourceDto } from '../gitea/dto';
import { AppsService } from './apps.service';
@@ -77,13 +77,17 @@ export class AppsController {
@Query('limit') limit?: string,
@Query('since') since?: string,
@Query('status') status?: string,
@Query('hideReleaseCandidates') hideReleaseCandidates?: string,
) {
return this.appsService.listBuildJobs(appKey, page, limit, since, status);
return this.appsService.listBuildJobs(appKey, page, limit, since, status, hideReleaseCandidates);
}
@Post('builds')
createBuildJob(@Body() body: CreateBuildJobDto) {
return this.appsService.createBuildJob(body);
createBuildJob(
@Body() body: CreateBuildJobDto,
@Req() request: { admin?: { id: string; name: string; email: string } },
) {
return this.appsService.createBuildJob(body, request.admin);
}
@Patch('builds/:buildJobId/cancel')
@@ -91,6 +95,11 @@ export class AppsController {
return this.appsService.cancelBuildJob(buildJobId);
}
@Patch('builds/:buildJobId/hide-release-candidate')
hideReleaseCandidate(@Param('buildJobId') buildJobId: string) {
return this.appsService.hideReleaseCandidate(buildJobId);
}
@Delete('builds/:buildJobId')
deleteBuildJob(@Param('buildJobId') buildJobId: string) {
return this.appsService.deleteBuildJob(buildJobId);
@@ -128,8 +137,16 @@ export class AppsController {
}
@Post('releases')
createRelease(@Body() body: CreateAppReleaseDto) {
return this.appsService.createRelease(body);
createRelease(
@Body() body: CreateAppReleaseDto,
@Req() request: { admin?: { id: string; name: string; email: string } },
) {
return this.appsService.createRelease(body, request.admin);
}
@Delete('releases/:releaseId')
deleteRelease(@Param('releaseId') releaseId: string) {
return this.appsService.deleteRelease(releaseId);
}
@Get('deployments')
@@ -143,8 +160,11 @@ export class AppsController {
}
@Post('deployments')
createDeploymentJob(@Body() body: CreateDeploymentJobDto) {
return this.appsService.createDeploymentJob(body);
createDeploymentJob(
@Body() body: CreateDeploymentJobDto,
@Req() request: { admin?: { id: string; name: string; email: string } },
) {
return this.appsService.createDeploymentJob(body, request.admin);
}
@Get('migrations')
@@ -158,8 +178,11 @@ export class AppsController {
}
@Post('migrations')
createMigrationJob(@Body() body: CreateMigrationJobDto) {
return this.appsService.createMigrationJob(body);
createMigrationJob(
@Body() body: CreateMigrationJobDto,
@Req() request: { admin?: { id: string; name: string; email: string } },
) {
return this.appsService.createMigrationJob(body, request.admin);
}
@Get('runtime-namespaces/:namespace')
@@ -172,6 +195,15 @@ export class AppsController {
return this.appsService.listRuntimeResources(namespace);
}
@Get('runtime-namespaces/:namespace/resources/:kind/:name/yaml')
getRuntimeResourceYaml(
@Param('namespace') namespace: string,
@Param('kind') kind: string,
@Param('name') name: string,
) {
return this.appsService.getRuntimeResourceYaml(namespace, kind, name);
}
@Post('runtime-namespaces')
ensureRuntimeNamespace(@Body() body: EnsureRuntimeNamespaceDto) {
return this.appsService.ensureRuntimeNamespace(body);
@@ -195,6 +227,11 @@ export class AppsController {
return this.appsService.createRuntimeProfile(body);
}
@Delete('runtime-profiles/:profileId')
deleteRuntimeProfile(@Param('profileId') profileId: string) {
return this.appsService.deleteRuntimeProfile(profileId);
}
@Post('runtime-profiles/:profileId/variables')
createRuntimeVariable(
@Param('profileId') profileId: string,
+503 -166
View File
File diff suppressed because it is too large Load Diff
+26 -4
View File
@@ -300,6 +300,25 @@ export class BuildRunnerService implements OnModuleInit, OnModuleDestroy {
const contextPath = this.configService.get<string>('BUILD_CONTEXT_PATH', '.');
const contextDir = path.resolve(workDir, contextPath);
const pushEnabled = this.configService.get<string>('BUILD_PUSH_ENABLED', 'true') !== 'false';
const viteBuildVariables = job.component.buildVariables
.filter((variable) => !variable.isSecret && variable.key.trim().startsWith('VITE_'));
const buildArgs = job.component.buildVariables
.filter((variable) => !variable.isSecret && variable.key.trim())
.filter((variable) => !variable.key.trim().startsWith('VITE_'))
.flatMap((variable) => ['--build-arg', `${variable.key}=${variable.value}`]);
if (viteBuildVariables.length > 0) {
await fs.writeFile(
path.join(contextDir, '.env.production.local'),
`${viteBuildVariables.map((variable) => `${variable.key}=${JSON.stringify(variable.value)}`).join('\n')}\n`,
'utf8',
);
await append(`[image] Vite build env ${viteBuildVariables.map((variable) => variable.key).join(', ')}`);
}
if (buildArgs.length > 0) {
await append(`[image] build args ${job.component.buildVariables.filter((variable) => !variable.isSecret && variable.key.trim() && !variable.key.trim().startsWith('VITE_')).map((variable) => variable.key).join(', ')}`);
}
if (image !== job.image) {
await this.prisma.buildJob.update({
@@ -316,7 +335,7 @@ export class BuildRunnerService implements OnModuleInit, OnModuleDestroy {
await runCommand({
jobId: job.id,
command: this.configService.get<string>('NERDCTL_BIN', 'nerdctl'),
args: [...prefix, 'build', '-t', image, '-f', dockerfile, '.'],
args: [...prefix, 'build', ...buildArgs, '-t', image, '-f', dockerfile, '.'],
cwd: contextDir,
append,
onProcess: (child) => this.activeProcesses.set(job.id, child),
@@ -340,7 +359,7 @@ export class BuildRunnerService implements OnModuleInit, OnModuleDestroy {
await runCommand({
jobId: job.id,
command: this.configService.get<string>('DOCKER_BIN', 'docker'),
args: ['build', '-t', image, '-f', dockerfile, '.'],
args: ['build', ...buildArgs, '-t', image, '-f', dockerfile, '.'],
cwd: contextDir,
append,
onProcess: (child) => this.activeProcesses.set(job.id, child),
@@ -428,11 +447,13 @@ export class BuildRunnerService implements OnModuleInit, OnModuleDestroy {
const destination = path.join(artifactDir, destinationName);
await fs.copyFile(artifact, destination);
const artifactSha256 = await sha256File(artifact);
const uploadedArtifact = await this.uploadArtifactIfConfigured(
job,
destination,
destinationName,
artifactVersion,
artifactSha256,
append,
);
const publicBaseUrl = this.configService.get<string>('BUILD_ARTIFACT_BASE_URL');
@@ -443,9 +464,10 @@ export class BuildRunnerService implements OnModuleInit, OnModuleDestroy {
await this.prisma.buildJob.update({
where: { id: job.id },
data: { artifact: artifactValue },
data: { artifact: artifactValue, artifactSha256 },
});
job.artifact = artifactValue;
job.artifactSha256 = artifactSha256;
await append(`[artifact] found ${path.relative(workDir, artifact)}`);
await append(`[artifact] copied to ${artifactValue}`);
@@ -456,6 +478,7 @@ export class BuildRunnerService implements OnModuleInit, OnModuleDestroy {
artifactPath: string,
artifactName: string,
artifactVersion: string | null,
sha256: string,
append: (line: string) => Promise<void>,
) {
const bucket = this.configService.get<string>('R2_BUCKET');
@@ -469,7 +492,6 @@ export class BuildRunnerService implements OnModuleInit, OnModuleDestroy {
const key = this.createArtifactObjectKey(job, artifactName, artifactVersion);
const stat = await fs.stat(artifactPath);
const sha256 = await sha256File(artifactPath);
const client = this.getR2Client(accessKeyId, secretAccessKey);
await append(`[r2] upload ${key}`);
+37 -2
View File
@@ -1,4 +1,5 @@
import { ArrayNotEmpty, IsArray, IsBoolean, IsEnum, IsInt, IsOptional, IsString, IsUUID, Min } from 'class-validator';
import { Type } from 'class-transformer';
import { ArrayNotEmpty, IsArray, IsBoolean, IsEnum, IsInt, IsOptional, IsString, IsUUID, Min, ValidateNested } from 'class-validator';
import { AppStatus, ComponentType } from './app-enums';
export class CreateAppDto {
@@ -115,6 +116,7 @@ export class CreateAppComponentDto {
name?: string;
port?: number;
targetPort?: number;
nodePort?: number;
protocol?: string;
}>;
}
@@ -194,9 +196,37 @@ export class CreateAppReleaseDto {
buildJobIds!: string[];
}
export class DeploymentResourceLimitsDto {
@IsOptional()
@Type(() => Number)
@IsInt()
replicas?: number;
@IsOptional()
@IsString()
cpuRequest?: string;
@IsOptional()
@IsString()
cpuLimit?: string;
@IsOptional()
@IsString()
memoryRequest?: string;
@IsOptional()
@IsString()
memoryLimit?: string;
}
export class CreateDeploymentJobDto {
@IsOptional()
@IsUUID()
releaseId!: string;
releaseId?: string;
@IsOptional()
@IsUUID()
buildJobId?: string;
@IsString()
environment!: string;
@@ -207,6 +237,11 @@ export class CreateDeploymentJobDto {
@IsOptional()
@IsBoolean()
updateLauncherAppVersion?: boolean;
@IsOptional()
@ValidateNested()
@Type(() => DeploymentResourceLimitsDto)
resources?: DeploymentResourceLimitsDto;
}
export class CreateRuntimeEnvProfileDto {
-1
View File
@@ -1 +0,0 @@
export * from "./index"
-5
View File
@@ -1,5 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
module.exports = { ...require('.') }
-1
View File
@@ -1 +0,0 @@
export * from "./index"
-5
View File
@@ -1,5 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
module.exports = { ...require('#main-entry-point') }
-1
View File
@@ -1 +0,0 @@
export * from "./default"
File diff suppressed because one or more lines are too long
-217
View File
@@ -1,217 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
Object.defineProperty(exports, "__esModule", { value: true });
const {
Decimal,
objectEnumValues,
makeStrictEnum,
Public,
getRuntime,
skip
} = require('./runtime/index-browser.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.19.3
* Query Engine version: c2990dca591cba766e3b7ef5d9e8a84796e47ab7
*/
Prisma.prismaVersion = {
client: "6.19.3",
engine: "c2990dca591cba766e3b7ef5d9e8a84796e47ab7"
}
Prisma.PrismaClientKnownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)};
Prisma.PrismaClientUnknownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientRustPanicError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientInitializationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientValidationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.empty = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.join = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.raw = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.defineExtension = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = objectEnumValues.instances.DbNull
Prisma.JsonNull = objectEnumValues.instances.JsonNull
Prisma.AnyNull = objectEnumValues.instances.AnyNull
Prisma.NullTypes = {
DbNull: objectEnumValues.classes.DbNull,
JsonNull: objectEnumValues.classes.JsonNull,
AnyNull: objectEnumValues.classes.AnyNull
}
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
});
exports.Prisma.UserScalarFieldEnum = {
id: 'id',
email: 'email',
passwordHash: 'passwordHash',
name: 'name',
role: 'role',
isActive: 'isActive',
lastLoginAt: 'lastLoginAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.LicenseScalarFieldEnum = {
id: 'id',
code: 'code',
name: 'name',
description: 'description',
defaultDurationDays: 'defaultDurationDays',
isActive: 'isActive',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.UserLicenseScalarFieldEnum = {
id: 'id',
userId: 'userId',
licenseId: 'licenseId',
startsAt: 'startsAt',
expiresAt: 'expiresAt',
assignedById: 'assignedById',
createdAt: 'createdAt'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.QueryMode = {
default: 'default',
insensitive: 'insensitive'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.UserRole = exports.$Enums.UserRole = {
ADMIN: 'ADMIN',
USER: 'USER'
};
exports.LicenseCode = exports.$Enums.LicenseCode = {
FREE: 'FREE',
EDUCATION: 'EDUCATION',
PROFESSIONAL: 'PROFESSIONAL'
};
exports.Prisma.ModelName = {
User: 'User',
License: 'License',
UserLicense: 'UserLicense'
};
/**
* This is a stub Prisma Client that will error at runtime if called.
*/
class PrismaClient {
constructor() {
return new Proxy(this, {
get(target, prop) {
let message
const runtime = getRuntime()
if (runtime.isEdge) {
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
- Use Prisma Accelerate: https://pris.ly/d/accelerate
- Use Driver Adapters: https://pris.ly/d/driver-adapters
`;
} else {
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
}
message += `
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
throw new Error(message)
}
})
}
}
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-183
View File
@@ -1,183 +0,0 @@
{
"name": "prisma-client-378612c311cf1c6fac598c21e6ea51068a0548bfbc7fdc08b65098bb7a2e5523",
"main": "index.js",
"types": "index.d.ts",
"browser": "default.js",
"exports": {
"./client": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./package.json": "./package.json",
".": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./edge": {
"types": "./edge.d.ts",
"require": "./edge.js",
"import": "./edge.js",
"default": "./edge.js"
},
"./react-native": {
"types": "./react-native.d.ts",
"require": "./react-native.js",
"import": "./react-native.js",
"default": "./react-native.js"
},
"./extension": {
"types": "./extension.d.ts",
"require": "./extension.js",
"import": "./extension.js",
"default": "./extension.js"
},
"./index-browser": {
"types": "./index.d.ts",
"require": "./index-browser.js",
"import": "./index-browser.js",
"default": "./index-browser.js"
},
"./index": {
"types": "./index.d.ts",
"require": "./index.js",
"import": "./index.js",
"default": "./index.js"
},
"./wasm": {
"types": "./wasm.d.ts",
"require": "./wasm.js",
"import": "./wasm.mjs",
"default": "./wasm.mjs"
},
"./runtime/client": {
"types": "./runtime/client.d.ts",
"node": {
"require": "./runtime/client.js",
"default": "./runtime/client.js"
},
"require": "./runtime/client.js",
"import": "./runtime/client.mjs",
"default": "./runtime/client.mjs"
},
"./runtime/library": {
"types": "./runtime/library.d.ts",
"require": "./runtime/library.js",
"import": "./runtime/library.mjs",
"default": "./runtime/library.mjs"
},
"./runtime/binary": {
"types": "./runtime/binary.d.ts",
"require": "./runtime/binary.js",
"import": "./runtime/binary.mjs",
"default": "./runtime/binary.mjs"
},
"./runtime/wasm-engine-edge": {
"types": "./runtime/wasm-engine-edge.d.ts",
"require": "./runtime/wasm-engine-edge.js",
"import": "./runtime/wasm-engine-edge.mjs",
"default": "./runtime/wasm-engine-edge.mjs"
},
"./runtime/wasm-compiler-edge": {
"types": "./runtime/wasm-compiler-edge.d.ts",
"require": "./runtime/wasm-compiler-edge.js",
"import": "./runtime/wasm-compiler-edge.mjs",
"default": "./runtime/wasm-compiler-edge.mjs"
},
"./runtime/edge": {
"types": "./runtime/edge.d.ts",
"require": "./runtime/edge.js",
"import": "./runtime/edge-esm.js",
"default": "./runtime/edge-esm.js"
},
"./runtime/react-native": {
"types": "./runtime/react-native.d.ts",
"require": "./runtime/react-native.js",
"import": "./runtime/react-native.js",
"default": "./runtime/react-native.js"
},
"./runtime/index-browser": {
"types": "./runtime/index-browser.d.ts",
"require": "./runtime/index-browser.js",
"import": "./runtime/index-browser.mjs",
"default": "./runtime/index-browser.mjs"
},
"./generator-build": {
"require": "./generator-build/index.js",
"import": "./generator-build/index.js",
"default": "./generator-build/index.js"
},
"./sql": {
"require": {
"types": "./sql.d.ts",
"node": "./sql.js",
"default": "./sql.js"
},
"import": {
"types": "./sql.d.ts",
"node": "./sql.mjs",
"default": "./sql.mjs"
},
"default": "./sql.js"
},
"./*": "./*"
},
"version": "6.19.3",
"sideEffects": false,
"imports": {
"#wasm-engine-loader": {
"edge-light": "./wasm-edge-light-loader.mjs",
"workerd": "./wasm-worker-loader.mjs",
"worker": "./wasm-worker-loader.mjs",
"default": "./wasm-worker-loader.mjs"
},
"#main-entry-point": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
}
}
}
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-370
View File
@@ -1,370 +0,0 @@
declare class AnyNull extends NullTypesEnumValue {
#private;
}
declare type Args<T, F extends Operation> = T extends {
[K: symbol]: {
types: {
operations: {
[K in F]: {
args: any;
};
};
};
};
} ? T[symbol]['types']['operations'][F]['args'] : any;
declare class DbNull extends NullTypesEnumValue {
#private;
}
export declare function Decimal(n: Decimal.Value): Decimal;
export declare namespace Decimal {
export type Constructor = typeof Decimal;
export type Instance = Decimal;
export type Rounding = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
export type Modulo = Rounding | 9;
export type Value = string | number | Decimal;
// http://mikemcl.github.io/decimal.js/#constructor-properties
export interface Config {
precision?: number;
rounding?: Rounding;
toExpNeg?: number;
toExpPos?: number;
minE?: number;
maxE?: number;
crypto?: boolean;
modulo?: Modulo;
defaults?: boolean;
}
}
export declare class Decimal {
readonly d: number[];
readonly e: number;
readonly s: number;
constructor(n: Decimal.Value);
absoluteValue(): Decimal;
abs(): Decimal;
ceil(): Decimal;
clampedTo(min: Decimal.Value, max: Decimal.Value): Decimal;
clamp(min: Decimal.Value, max: Decimal.Value): Decimal;
comparedTo(n: Decimal.Value): number;
cmp(n: Decimal.Value): number;
cosine(): Decimal;
cos(): Decimal;
cubeRoot(): Decimal;
cbrt(): Decimal;
decimalPlaces(): number;
dp(): number;
dividedBy(n: Decimal.Value): Decimal;
div(n: Decimal.Value): Decimal;
dividedToIntegerBy(n: Decimal.Value): Decimal;
divToInt(n: Decimal.Value): Decimal;
equals(n: Decimal.Value): boolean;
eq(n: Decimal.Value): boolean;
floor(): Decimal;
greaterThan(n: Decimal.Value): boolean;
gt(n: Decimal.Value): boolean;
greaterThanOrEqualTo(n: Decimal.Value): boolean;
gte(n: Decimal.Value): boolean;
hyperbolicCosine(): Decimal;
cosh(): Decimal;
hyperbolicSine(): Decimal;
sinh(): Decimal;
hyperbolicTangent(): Decimal;
tanh(): Decimal;
inverseCosine(): Decimal;
acos(): Decimal;
inverseHyperbolicCosine(): Decimal;
acosh(): Decimal;
inverseHyperbolicSine(): Decimal;
asinh(): Decimal;
inverseHyperbolicTangent(): Decimal;
atanh(): Decimal;
inverseSine(): Decimal;
asin(): Decimal;
inverseTangent(): Decimal;
atan(): Decimal;
isFinite(): boolean;
isInteger(): boolean;
isInt(): boolean;
isNaN(): boolean;
isNegative(): boolean;
isNeg(): boolean;
isPositive(): boolean;
isPos(): boolean;
isZero(): boolean;
lessThan(n: Decimal.Value): boolean;
lt(n: Decimal.Value): boolean;
lessThanOrEqualTo(n: Decimal.Value): boolean;
lte(n: Decimal.Value): boolean;
logarithm(n?: Decimal.Value): Decimal;
log(n?: Decimal.Value): Decimal;
minus(n: Decimal.Value): Decimal;
sub(n: Decimal.Value): Decimal;
modulo(n: Decimal.Value): Decimal;
mod(n: Decimal.Value): Decimal;
naturalExponential(): Decimal;
exp(): Decimal;
naturalLogarithm(): Decimal;
ln(): Decimal;
negated(): Decimal;
neg(): Decimal;
plus(n: Decimal.Value): Decimal;
add(n: Decimal.Value): Decimal;
precision(includeZeros?: boolean): number;
sd(includeZeros?: boolean): number;
round(): Decimal;
sine() : Decimal;
sin() : Decimal;
squareRoot(): Decimal;
sqrt(): Decimal;
tangent() : Decimal;
tan() : Decimal;
times(n: Decimal.Value): Decimal;
mul(n: Decimal.Value) : Decimal;
toBinary(significantDigits?: number): string;
toBinary(significantDigits: number, rounding: Decimal.Rounding): string;
toDecimalPlaces(decimalPlaces?: number): Decimal;
toDecimalPlaces(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
toDP(decimalPlaces?: number): Decimal;
toDP(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
toExponential(decimalPlaces?: number): string;
toExponential(decimalPlaces: number, rounding: Decimal.Rounding): string;
toFixed(decimalPlaces?: number): string;
toFixed(decimalPlaces: number, rounding: Decimal.Rounding): string;
toFraction(max_denominator?: Decimal.Value): Decimal[];
toHexadecimal(significantDigits?: number): string;
toHexadecimal(significantDigits: number, rounding: Decimal.Rounding): string;
toHex(significantDigits?: number): string;
toHex(significantDigits: number, rounding?: Decimal.Rounding): string;
toJSON(): string;
toNearest(n: Decimal.Value, rounding?: Decimal.Rounding): Decimal;
toNumber(): number;
toOctal(significantDigits?: number): string;
toOctal(significantDigits: number, rounding: Decimal.Rounding): string;
toPower(n: Decimal.Value): Decimal;
pow(n: Decimal.Value): Decimal;
toPrecision(significantDigits?: number): string;
toPrecision(significantDigits: number, rounding: Decimal.Rounding): string;
toSignificantDigits(significantDigits?: number): Decimal;
toSignificantDigits(significantDigits: number, rounding: Decimal.Rounding): Decimal;
toSD(significantDigits?: number): Decimal;
toSD(significantDigits: number, rounding: Decimal.Rounding): Decimal;
toString(): string;
truncated(): Decimal;
trunc(): Decimal;
valueOf(): string;
static abs(n: Decimal.Value): Decimal;
static acos(n: Decimal.Value): Decimal;
static acosh(n: Decimal.Value): Decimal;
static add(x: Decimal.Value, y: Decimal.Value): Decimal;
static asin(n: Decimal.Value): Decimal;
static asinh(n: Decimal.Value): Decimal;
static atan(n: Decimal.Value): Decimal;
static atanh(n: Decimal.Value): Decimal;
static atan2(y: Decimal.Value, x: Decimal.Value): Decimal;
static cbrt(n: Decimal.Value): Decimal;
static ceil(n: Decimal.Value): Decimal;
static clamp(n: Decimal.Value, min: Decimal.Value, max: Decimal.Value): Decimal;
static clone(object?: Decimal.Config): Decimal.Constructor;
static config(object: Decimal.Config): Decimal.Constructor;
static cos(n: Decimal.Value): Decimal;
static cosh(n: Decimal.Value): Decimal;
static div(x: Decimal.Value, y: Decimal.Value): Decimal;
static exp(n: Decimal.Value): Decimal;
static floor(n: Decimal.Value): Decimal;
static hypot(...n: Decimal.Value[]): Decimal;
static isDecimal(object: any): object is Decimal;
static ln(n: Decimal.Value): Decimal;
static log(n: Decimal.Value, base?: Decimal.Value): Decimal;
static log2(n: Decimal.Value): Decimal;
static log10(n: Decimal.Value): Decimal;
static max(...n: Decimal.Value[]): Decimal;
static min(...n: Decimal.Value[]): Decimal;
static mod(x: Decimal.Value, y: Decimal.Value): Decimal;
static mul(x: Decimal.Value, y: Decimal.Value): Decimal;
static noConflict(): Decimal.Constructor; // Browser only
static pow(base: Decimal.Value, exponent: Decimal.Value): Decimal;
static random(significantDigits?: number): Decimal;
static round(n: Decimal.Value): Decimal;
static set(object: Decimal.Config): Decimal.Constructor;
static sign(n: Decimal.Value): number;
static sin(n: Decimal.Value): Decimal;
static sinh(n: Decimal.Value): Decimal;
static sqrt(n: Decimal.Value): Decimal;
static sub(x: Decimal.Value, y: Decimal.Value): Decimal;
static sum(...n: Decimal.Value[]): Decimal;
static tan(n: Decimal.Value): Decimal;
static tanh(n: Decimal.Value): Decimal;
static trunc(n: Decimal.Value): Decimal;
static readonly default?: Decimal.Constructor;
static readonly Decimal?: Decimal.Constructor;
static readonly precision: number;
static readonly rounding: Decimal.Rounding;
static readonly toExpNeg: number;
static readonly toExpPos: number;
static readonly minE: number;
static readonly maxE: number;
static readonly crypto: boolean;
static readonly modulo: Decimal.Modulo;
static readonly ROUND_UP: 0;
static readonly ROUND_DOWN: 1;
static readonly ROUND_CEIL: 2;
static readonly ROUND_FLOOR: 3;
static readonly ROUND_HALF_UP: 4;
static readonly ROUND_HALF_DOWN: 5;
static readonly ROUND_HALF_EVEN: 6;
static readonly ROUND_HALF_CEIL: 7;
static readonly ROUND_HALF_FLOOR: 8;
static readonly EUCLID: 9;
}
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
[K in keyof A]: Exact<A[K], W[K]>;
} : W) : never) | (A extends Narrowable ? A : never);
export declare function getRuntime(): GetRuntimeOutput;
declare type GetRuntimeOutput = {
id: RuntimeName;
prettyName: string;
isEdge: boolean;
};
declare class JsonNull extends NullTypesEnumValue {
#private;
}
/**
* Generates more strict variant of an enum which, unlike regular enum,
* throws on non-existing property access. This can be useful in following situations:
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
* - enum values are generated dynamically from DMMF.
*
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
* will result in `undefined` value being used, which will be accepted. Using strict enum
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
*
* Note: if you need to check for existence of a value in the enum you can still use either
* `in` operator or `hasOwnProperty` function.
*
* @param definition
* @returns
*/
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
declare type Narrowable = string | number | bigint | boolean | [];
declare class NullTypesEnumValue extends ObjectEnumValue {
_getNamespace(): string;
}
/**
* Base class for unique values of object-valued enums.
*/
declare abstract class ObjectEnumValue {
constructor(arg?: symbol);
abstract _getNamespace(): string;
_getName(): string;
toString(): string;
}
export declare const objectEnumValues: {
classes: {
DbNull: typeof DbNull;
JsonNull: typeof JsonNull;
AnyNull: typeof AnyNull;
};
instances: {
DbNull: DbNull;
JsonNull: JsonNull;
AnyNull: AnyNull;
};
};
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
declare namespace Public {
export {
validator
}
}
export { Public }
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
export { }
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-66
View File
@@ -1,66 +0,0 @@
generator authClient {
provider = "prisma-client-js"
output = "../src/generated/auth-client"
}
datasource db {
provider = "postgresql"
url = env("AUTH_DATABASE_URL")
}
enum UserRole {
ADMIN
USER
}
enum LicenseCode {
FREE
EDUCATION
PROFESSIONAL
}
model User {
id String @id @default(uuid()) @db.Uuid
email String @unique
passwordHash String @map("password_hash")
name String?
role UserRole @default(USER)
isActive Boolean @default(true) @map("is_active")
lastLoginAt DateTime? @map("last_login_at") @db.Timestamptz(3)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
userLicenses UserLicense[]
@@map("users")
}
model License {
id String @id @default(uuid()) @db.Uuid
code LicenseCode @unique
name String
description String?
defaultDurationDays Int? @default(365) @map("default_duration_days")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
userLicenses UserLicense[]
@@index([isActive])
@@map("licenses")
}
model UserLicense {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
licenseId String @map("license_id") @db.Uuid
startsAt DateTime @default(now()) @map("starts_at") @db.Timestamptz(3)
expiresAt DateTime? @map("expires_at") @db.Timestamptz(3)
assignedById String? @map("assigned_by") @db.Uuid
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
license License @relation(fields: [licenseId], references: [id], onDelete: Restrict)
@@index([userId, startsAt])
@@index([licenseId])
@@map("user_licenses")
}
@@ -1,5 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
export default import('./query_engine_bg.wasm?module')
@@ -1,5 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
export default import('./query_engine_bg.wasm')
-1
View File
@@ -1 +0,0 @@
export * from "./default"
-238
View File
@@ -1,238 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
Object.defineProperty(exports, "__esModule", { value: true });
const {
PrismaClientKnownRequestError,
PrismaClientUnknownRequestError,
PrismaClientRustPanicError,
PrismaClientInitializationError,
PrismaClientValidationError,
getPrismaClient,
sqltag,
empty,
join,
raw,
skip,
Decimal,
Debug,
objectEnumValues,
makeStrictEnum,
Extensions,
warnOnce,
defineDmmfProperty,
Public,
getRuntime,
createParam,
} = require('./runtime/wasm-engine-edge.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.19.3
* Query Engine version: c2990dca591cba766e3b7ef5d9e8a84796e47ab7
*/
Prisma.prismaVersion = {
client: "6.19.3",
engine: "c2990dca591cba766e3b7ef5d9e8a84796e47ab7"
}
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
Prisma.PrismaClientUnknownRequestError = PrismaClientUnknownRequestError
Prisma.PrismaClientRustPanicError = PrismaClientRustPanicError
Prisma.PrismaClientInitializationError = PrismaClientInitializationError
Prisma.PrismaClientValidationError = PrismaClientValidationError
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = sqltag
Prisma.empty = empty
Prisma.join = join
Prisma.raw = raw
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = Extensions.getExtensionContext
Prisma.defineExtension = Extensions.defineExtension
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = objectEnumValues.instances.DbNull
Prisma.JsonNull = objectEnumValues.instances.JsonNull
Prisma.AnyNull = objectEnumValues.instances.AnyNull
Prisma.NullTypes = {
DbNull: objectEnumValues.classes.DbNull,
JsonNull: objectEnumValues.classes.JsonNull,
AnyNull: objectEnumValues.classes.AnyNull
}
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
});
exports.Prisma.UserScalarFieldEnum = {
id: 'id',
email: 'email',
passwordHash: 'passwordHash',
name: 'name',
role: 'role',
isActive: 'isActive',
lastLoginAt: 'lastLoginAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.LicenseScalarFieldEnum = {
id: 'id',
code: 'code',
name: 'name',
description: 'description',
defaultDurationDays: 'defaultDurationDays',
isActive: 'isActive',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.UserLicenseScalarFieldEnum = {
id: 'id',
userId: 'userId',
licenseId: 'licenseId',
startsAt: 'startsAt',
expiresAt: 'expiresAt',
assignedById: 'assignedById',
createdAt: 'createdAt'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.QueryMode = {
default: 'default',
insensitive: 'insensitive'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.UserRole = exports.$Enums.UserRole = {
ADMIN: 'ADMIN',
USER: 'USER'
};
exports.LicenseCode = exports.$Enums.LicenseCode = {
FREE: 'FREE',
EDUCATION: 'EDUCATION',
PROFESSIONAL: 'PROFESSIONAL'
};
exports.Prisma.ModelName = {
User: 'User',
License: 'License',
UserLicense: 'UserLicense'
};
/**
* Create the Client
*/
const config = {
"generator": {
"name": "authClient",
"provider": {
"fromEnvVar": null,
"value": "prisma-client-js"
},
"output": {
"value": "/home/chyoo/tmp/atnmy-stdo-v2/atnmy-stdo-adm-v2-be/src/generated/auth-client",
"fromEnvVar": null
},
"config": {
"engineType": "library"
},
"binaryTargets": [
{
"fromEnvVar": null,
"value": "debian-openssl-3.0.x",
"native": true
}
],
"previewFeatures": [],
"sourceFilePath": "/home/chyoo/tmp/atnmy-stdo-v2/atnmy-stdo-adm-v2-be/prisma/auth.schema.prisma",
"isCustomOutput": true
},
"relativeEnvPaths": {
"rootEnvPath": null,
"schemaEnvPath": "../../../.env"
},
"relativePath": "../../../prisma",
"clientVersion": "6.19.3",
"engineVersion": "c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
"datasourceNames": [
"db"
],
"activeProvider": "postgresql",
"postinstall": false,
"inlineDatasources": {
"db": {
"url": {
"fromEnvVar": "AUTH_DATABASE_URL",
"value": null
}
}
},
"inlineSchema": "generator authClient {\n provider = \"prisma-client-js\"\n output = \"../src/generated/auth-client\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"AUTH_DATABASE_URL\")\n}\n\nenum UserRole {\n ADMIN\n USER\n}\n\nenum LicenseCode {\n FREE\n EDUCATION\n PROFESSIONAL\n}\n\nmodel User {\n id String @id @default(uuid()) @db.Uuid\n email String @unique\n passwordHash String @map(\"password_hash\")\n name String?\n role UserRole @default(USER)\n isActive Boolean @default(true) @map(\"is_active\")\n lastLoginAt DateTime? @map(\"last_login_at\") @db.Timestamptz(3)\n createdAt DateTime @default(now()) @map(\"created_at\") @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @map(\"updated_at\") @db.Timestamptz(3)\n userLicenses UserLicense[]\n\n @@map(\"users\")\n}\n\nmodel License {\n id String @id @default(uuid()) @db.Uuid\n code LicenseCode @unique\n name String\n description String?\n defaultDurationDays Int? @default(365) @map(\"default_duration_days\")\n isActive Boolean @default(true) @map(\"is_active\")\n createdAt DateTime @default(now()) @map(\"created_at\") @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @map(\"updated_at\") @db.Timestamptz(3)\n userLicenses UserLicense[]\n\n @@index([isActive])\n @@map(\"licenses\")\n}\n\nmodel UserLicense {\n id String @id @default(uuid()) @db.Uuid\n userId String @map(\"user_id\") @db.Uuid\n licenseId String @map(\"license_id\") @db.Uuid\n startsAt DateTime @default(now()) @map(\"starts_at\") @db.Timestamptz(3)\n expiresAt DateTime? @map(\"expires_at\") @db.Timestamptz(3)\n assignedById String? @map(\"assigned_by\") @db.Uuid\n createdAt DateTime @default(now()) @map(\"created_at\") @db.Timestamptz(3)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n license License @relation(fields: [licenseId], references: [id], onDelete: Restrict)\n\n @@index([userId, startsAt])\n @@index([licenseId])\n @@map(\"user_licenses\")\n}\n",
"inlineSchemaHash": "b41ead22c04f404adbe239bd44c2751b9ed9adf3c5b7b7013be05252086f79d7",
"copyEngine": true
}
config.dirname = '/'
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"passwordHash\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"password_hash\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"UserRole\"},{\"name\":\"isActive\",\"kind\":\"scalar\",\"type\":\"Boolean\",\"dbName\":\"is_active\"},{\"name\":\"lastLoginAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"last_login_at\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"userLicenses\",\"kind\":\"object\",\"type\":\"UserLicense\",\"relationName\":\"UserToUserLicense\"}],\"dbName\":\"users\"},\"License\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"code\",\"kind\":\"enum\",\"type\":\"LicenseCode\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"defaultDurationDays\",\"kind\":\"scalar\",\"type\":\"Int\",\"dbName\":\"default_duration_days\"},{\"name\":\"isActive\",\"kind\":\"scalar\",\"type\":\"Boolean\",\"dbName\":\"is_active\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"userLicenses\",\"kind\":\"object\",\"type\":\"UserLicense\",\"relationName\":\"LicenseToUserLicense\"}],\"dbName\":\"licenses\"},\"UserLicense\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"user_id\"},{\"name\":\"licenseId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"license_id\"},{\"name\":\"startsAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"starts_at\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"expires_at\"},{\"name\":\"assignedById\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"assigned_by\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToUserLicense\"},{\"name\":\"license\",\"kind\":\"object\",\"type\":\"License\",\"relationName\":\"LicenseToUserLicense\"}],\"dbName\":\"user_licenses\"}},\"enums\":{},\"types\":{}}")
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
config.engineWasm = {
getRuntime: async () => require('./query_engine_bg.js'),
getQueryEngineWasmModule: async () => {
const loader = (await import('#wasm-engine-loader')).default
const engine = (await loader).default
return engine
}
}
config.compilerWasm = undefined
config.injectableEdgeEnv = () => ({
parsed: {
AUTH_DATABASE_URL: typeof globalThis !== 'undefined' && globalThis['AUTH_DATABASE_URL'] || typeof process !== 'undefined' && process.env && process.env.AUTH_DATABASE_URL || undefined
}
})
if (typeof globalThis !== 'undefined' && globalThis['DEBUG'] || typeof process !== 'undefined' && process.env && process.env.DEBUG || undefined) {
Debug.enable(typeof globalThis !== 'undefined' && globalThis['DEBUG'] || typeof process !== 'undefined' && process.env && process.env.DEBUG || undefined)
}
const PrismaClient = getPrismaClient(config)
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)
-1
View File
@@ -1 +0,0 @@
export * from "./index"
-5
View File
@@ -1,5 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
module.exports = { ...require('.') }
-1
View File
@@ -1 +0,0 @@
export * from "./index"
-5
View File
@@ -1,5 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
module.exports = { ...require('#main-entry-point') }
-1
View File
@@ -1 +0,0 @@
export * from "./default"
File diff suppressed because one or more lines are too long
@@ -1,233 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
Object.defineProperty(exports, "__esModule", { value: true });
const {
Decimal,
objectEnumValues,
makeStrictEnum,
Public,
getRuntime,
skip
} = require('./runtime/index-browser.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.19.3
* Query Engine version: c2990dca591cba766e3b7ef5d9e8a84796e47ab7
*/
Prisma.prismaVersion = {
client: "6.19.3",
engine: "c2990dca591cba766e3b7ef5d9e8a84796e47ab7"
}
Prisma.PrismaClientKnownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)};
Prisma.PrismaClientUnknownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientRustPanicError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientInitializationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientValidationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.empty = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.join = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.raw = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.defineExtension = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = objectEnumValues.instances.DbNull
Prisma.JsonNull = objectEnumValues.instances.JsonNull
Prisma.AnyNull = objectEnumValues.instances.AnyNull
Prisma.NullTypes = {
DbNull: objectEnumValues.classes.DbNull,
JsonNull: objectEnumValues.classes.JsonNull,
AnyNull: objectEnumValues.classes.AnyNull
}
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
});
exports.Prisma.LauncherNoticeScalarFieldEnum = {
id: 'id',
appKey: 'appKey',
title: 'title',
body: 'body',
linkUrl: 'linkUrl',
isActive: 'isActive',
startsAt: 'startsAt',
endsAt: 'endsAt',
priority: 'priority',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.ProvisionedDeviceScalarFieldEnum = {
id: 'id',
accountEmail: 'accountEmail',
authUserId: 'authUserId',
hostname: 'hostname',
tailscaleHostname: 'tailscaleHostname',
sshUser: 'sshUser',
workspaceRoot: 'workspaceRoot',
usageType: 'usageType',
os: 'os',
arch: 'arch',
kernel: 'kernel',
distro: 'distro',
device: 'device',
installedAt: 'installedAt',
lastProvisionedAt: 'lastProvisionedAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.LauncherSshKeyScalarFieldEnum = {
id: 'id',
accountEmail: 'accountEmail',
authUserId: 'authUserId',
publicKey: 'publicKey',
publicKeyHash: 'publicKeyHash',
keyType: 'keyType',
label: 'label',
lastProvisionedAt: 'lastProvisionedAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.NullableJsonNullValueInput = {
DbNull: Prisma.DbNull,
JsonNull: Prisma.JsonNull
};
exports.Prisma.QueryMode = {
default: 'default',
insensitive: 'insensitive'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.Prisma.JsonNullValueFilter = {
DbNull: Prisma.DbNull,
JsonNull: Prisma.JsonNull,
AnyNull: Prisma.AnyNull
};
exports.Prisma.ModelName = {
LauncherNotice: 'LauncherNotice',
ProvisionedDevice: 'ProvisionedDevice',
LauncherSshKey: 'LauncherSshKey'
};
/**
* This is a stub Prisma Client that will error at runtime if called.
*/
class PrismaClient {
constructor() {
return new Proxy(this, {
get(target, prop) {
let message
const runtime = getRuntime()
if (runtime.isEdge) {
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
- Use Prisma Accelerate: https://pris.ly/d/accelerate
- Use Driver Adapters: https://pris.ly/d/driver-adapters
`;
} else {
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
}
message += `
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
throw new Error(message)
}
})
}
}
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-183
View File
@@ -1,183 +0,0 @@
{
"name": "prisma-client-2cc4fd701019d0254ce12699ae25121c6ed85eba27006e31abbdac7779ae92f0",
"main": "index.js",
"types": "index.d.ts",
"browser": "default.js",
"exports": {
"./client": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./package.json": "./package.json",
".": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./edge": {
"types": "./edge.d.ts",
"require": "./edge.js",
"import": "./edge.js",
"default": "./edge.js"
},
"./react-native": {
"types": "./react-native.d.ts",
"require": "./react-native.js",
"import": "./react-native.js",
"default": "./react-native.js"
},
"./extension": {
"types": "./extension.d.ts",
"require": "./extension.js",
"import": "./extension.js",
"default": "./extension.js"
},
"./index-browser": {
"types": "./index.d.ts",
"require": "./index-browser.js",
"import": "./index-browser.js",
"default": "./index-browser.js"
},
"./index": {
"types": "./index.d.ts",
"require": "./index.js",
"import": "./index.js",
"default": "./index.js"
},
"./wasm": {
"types": "./wasm.d.ts",
"require": "./wasm.js",
"import": "./wasm.mjs",
"default": "./wasm.mjs"
},
"./runtime/client": {
"types": "./runtime/client.d.ts",
"node": {
"require": "./runtime/client.js",
"default": "./runtime/client.js"
},
"require": "./runtime/client.js",
"import": "./runtime/client.mjs",
"default": "./runtime/client.mjs"
},
"./runtime/library": {
"types": "./runtime/library.d.ts",
"require": "./runtime/library.js",
"import": "./runtime/library.mjs",
"default": "./runtime/library.mjs"
},
"./runtime/binary": {
"types": "./runtime/binary.d.ts",
"require": "./runtime/binary.js",
"import": "./runtime/binary.mjs",
"default": "./runtime/binary.mjs"
},
"./runtime/wasm-engine-edge": {
"types": "./runtime/wasm-engine-edge.d.ts",
"require": "./runtime/wasm-engine-edge.js",
"import": "./runtime/wasm-engine-edge.mjs",
"default": "./runtime/wasm-engine-edge.mjs"
},
"./runtime/wasm-compiler-edge": {
"types": "./runtime/wasm-compiler-edge.d.ts",
"require": "./runtime/wasm-compiler-edge.js",
"import": "./runtime/wasm-compiler-edge.mjs",
"default": "./runtime/wasm-compiler-edge.mjs"
},
"./runtime/edge": {
"types": "./runtime/edge.d.ts",
"require": "./runtime/edge.js",
"import": "./runtime/edge-esm.js",
"default": "./runtime/edge-esm.js"
},
"./runtime/react-native": {
"types": "./runtime/react-native.d.ts",
"require": "./runtime/react-native.js",
"import": "./runtime/react-native.js",
"default": "./runtime/react-native.js"
},
"./runtime/index-browser": {
"types": "./runtime/index-browser.d.ts",
"require": "./runtime/index-browser.js",
"import": "./runtime/index-browser.mjs",
"default": "./runtime/index-browser.mjs"
},
"./generator-build": {
"require": "./generator-build/index.js",
"import": "./generator-build/index.js",
"default": "./generator-build/index.js"
},
"./sql": {
"require": {
"types": "./sql.d.ts",
"node": "./sql.js",
"default": "./sql.js"
},
"import": {
"types": "./sql.d.ts",
"node": "./sql.mjs",
"default": "./sql.mjs"
},
"default": "./sql.js"
},
"./*": "./*"
},
"version": "6.19.3",
"sideEffects": false,
"imports": {
"#wasm-engine-loader": {
"edge-light": "./wasm-edge-light-loader.mjs",
"workerd": "./wasm-worker-loader.mjs",
"worker": "./wasm-worker-loader.mjs",
"default": "./wasm-worker-loader.mjs"
},
"#main-entry-point": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
}
}
}
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-370
View File
@@ -1,370 +0,0 @@
declare class AnyNull extends NullTypesEnumValue {
#private;
}
declare type Args<T, F extends Operation> = T extends {
[K: symbol]: {
types: {
operations: {
[K in F]: {
args: any;
};
};
};
};
} ? T[symbol]['types']['operations'][F]['args'] : any;
declare class DbNull extends NullTypesEnumValue {
#private;
}
export declare function Decimal(n: Decimal.Value): Decimal;
export declare namespace Decimal {
export type Constructor = typeof Decimal;
export type Instance = Decimal;
export type Rounding = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
export type Modulo = Rounding | 9;
export type Value = string | number | Decimal;
// http://mikemcl.github.io/decimal.js/#constructor-properties
export interface Config {
precision?: number;
rounding?: Rounding;
toExpNeg?: number;
toExpPos?: number;
minE?: number;
maxE?: number;
crypto?: boolean;
modulo?: Modulo;
defaults?: boolean;
}
}
export declare class Decimal {
readonly d: number[];
readonly e: number;
readonly s: number;
constructor(n: Decimal.Value);
absoluteValue(): Decimal;
abs(): Decimal;
ceil(): Decimal;
clampedTo(min: Decimal.Value, max: Decimal.Value): Decimal;
clamp(min: Decimal.Value, max: Decimal.Value): Decimal;
comparedTo(n: Decimal.Value): number;
cmp(n: Decimal.Value): number;
cosine(): Decimal;
cos(): Decimal;
cubeRoot(): Decimal;
cbrt(): Decimal;
decimalPlaces(): number;
dp(): number;
dividedBy(n: Decimal.Value): Decimal;
div(n: Decimal.Value): Decimal;
dividedToIntegerBy(n: Decimal.Value): Decimal;
divToInt(n: Decimal.Value): Decimal;
equals(n: Decimal.Value): boolean;
eq(n: Decimal.Value): boolean;
floor(): Decimal;
greaterThan(n: Decimal.Value): boolean;
gt(n: Decimal.Value): boolean;
greaterThanOrEqualTo(n: Decimal.Value): boolean;
gte(n: Decimal.Value): boolean;
hyperbolicCosine(): Decimal;
cosh(): Decimal;
hyperbolicSine(): Decimal;
sinh(): Decimal;
hyperbolicTangent(): Decimal;
tanh(): Decimal;
inverseCosine(): Decimal;
acos(): Decimal;
inverseHyperbolicCosine(): Decimal;
acosh(): Decimal;
inverseHyperbolicSine(): Decimal;
asinh(): Decimal;
inverseHyperbolicTangent(): Decimal;
atanh(): Decimal;
inverseSine(): Decimal;
asin(): Decimal;
inverseTangent(): Decimal;
atan(): Decimal;
isFinite(): boolean;
isInteger(): boolean;
isInt(): boolean;
isNaN(): boolean;
isNegative(): boolean;
isNeg(): boolean;
isPositive(): boolean;
isPos(): boolean;
isZero(): boolean;
lessThan(n: Decimal.Value): boolean;
lt(n: Decimal.Value): boolean;
lessThanOrEqualTo(n: Decimal.Value): boolean;
lte(n: Decimal.Value): boolean;
logarithm(n?: Decimal.Value): Decimal;
log(n?: Decimal.Value): Decimal;
minus(n: Decimal.Value): Decimal;
sub(n: Decimal.Value): Decimal;
modulo(n: Decimal.Value): Decimal;
mod(n: Decimal.Value): Decimal;
naturalExponential(): Decimal;
exp(): Decimal;
naturalLogarithm(): Decimal;
ln(): Decimal;
negated(): Decimal;
neg(): Decimal;
plus(n: Decimal.Value): Decimal;
add(n: Decimal.Value): Decimal;
precision(includeZeros?: boolean): number;
sd(includeZeros?: boolean): number;
round(): Decimal;
sine() : Decimal;
sin() : Decimal;
squareRoot(): Decimal;
sqrt(): Decimal;
tangent() : Decimal;
tan() : Decimal;
times(n: Decimal.Value): Decimal;
mul(n: Decimal.Value) : Decimal;
toBinary(significantDigits?: number): string;
toBinary(significantDigits: number, rounding: Decimal.Rounding): string;
toDecimalPlaces(decimalPlaces?: number): Decimal;
toDecimalPlaces(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
toDP(decimalPlaces?: number): Decimal;
toDP(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
toExponential(decimalPlaces?: number): string;
toExponential(decimalPlaces: number, rounding: Decimal.Rounding): string;
toFixed(decimalPlaces?: number): string;
toFixed(decimalPlaces: number, rounding: Decimal.Rounding): string;
toFraction(max_denominator?: Decimal.Value): Decimal[];
toHexadecimal(significantDigits?: number): string;
toHexadecimal(significantDigits: number, rounding: Decimal.Rounding): string;
toHex(significantDigits?: number): string;
toHex(significantDigits: number, rounding?: Decimal.Rounding): string;
toJSON(): string;
toNearest(n: Decimal.Value, rounding?: Decimal.Rounding): Decimal;
toNumber(): number;
toOctal(significantDigits?: number): string;
toOctal(significantDigits: number, rounding: Decimal.Rounding): string;
toPower(n: Decimal.Value): Decimal;
pow(n: Decimal.Value): Decimal;
toPrecision(significantDigits?: number): string;
toPrecision(significantDigits: number, rounding: Decimal.Rounding): string;
toSignificantDigits(significantDigits?: number): Decimal;
toSignificantDigits(significantDigits: number, rounding: Decimal.Rounding): Decimal;
toSD(significantDigits?: number): Decimal;
toSD(significantDigits: number, rounding: Decimal.Rounding): Decimal;
toString(): string;
truncated(): Decimal;
trunc(): Decimal;
valueOf(): string;
static abs(n: Decimal.Value): Decimal;
static acos(n: Decimal.Value): Decimal;
static acosh(n: Decimal.Value): Decimal;
static add(x: Decimal.Value, y: Decimal.Value): Decimal;
static asin(n: Decimal.Value): Decimal;
static asinh(n: Decimal.Value): Decimal;
static atan(n: Decimal.Value): Decimal;
static atanh(n: Decimal.Value): Decimal;
static atan2(y: Decimal.Value, x: Decimal.Value): Decimal;
static cbrt(n: Decimal.Value): Decimal;
static ceil(n: Decimal.Value): Decimal;
static clamp(n: Decimal.Value, min: Decimal.Value, max: Decimal.Value): Decimal;
static clone(object?: Decimal.Config): Decimal.Constructor;
static config(object: Decimal.Config): Decimal.Constructor;
static cos(n: Decimal.Value): Decimal;
static cosh(n: Decimal.Value): Decimal;
static div(x: Decimal.Value, y: Decimal.Value): Decimal;
static exp(n: Decimal.Value): Decimal;
static floor(n: Decimal.Value): Decimal;
static hypot(...n: Decimal.Value[]): Decimal;
static isDecimal(object: any): object is Decimal;
static ln(n: Decimal.Value): Decimal;
static log(n: Decimal.Value, base?: Decimal.Value): Decimal;
static log2(n: Decimal.Value): Decimal;
static log10(n: Decimal.Value): Decimal;
static max(...n: Decimal.Value[]): Decimal;
static min(...n: Decimal.Value[]): Decimal;
static mod(x: Decimal.Value, y: Decimal.Value): Decimal;
static mul(x: Decimal.Value, y: Decimal.Value): Decimal;
static noConflict(): Decimal.Constructor; // Browser only
static pow(base: Decimal.Value, exponent: Decimal.Value): Decimal;
static random(significantDigits?: number): Decimal;
static round(n: Decimal.Value): Decimal;
static set(object: Decimal.Config): Decimal.Constructor;
static sign(n: Decimal.Value): number;
static sin(n: Decimal.Value): Decimal;
static sinh(n: Decimal.Value): Decimal;
static sqrt(n: Decimal.Value): Decimal;
static sub(x: Decimal.Value, y: Decimal.Value): Decimal;
static sum(...n: Decimal.Value[]): Decimal;
static tan(n: Decimal.Value): Decimal;
static tanh(n: Decimal.Value): Decimal;
static trunc(n: Decimal.Value): Decimal;
static readonly default?: Decimal.Constructor;
static readonly Decimal?: Decimal.Constructor;
static readonly precision: number;
static readonly rounding: Decimal.Rounding;
static readonly toExpNeg: number;
static readonly toExpPos: number;
static readonly minE: number;
static readonly maxE: number;
static readonly crypto: boolean;
static readonly modulo: Decimal.Modulo;
static readonly ROUND_UP: 0;
static readonly ROUND_DOWN: 1;
static readonly ROUND_CEIL: 2;
static readonly ROUND_FLOOR: 3;
static readonly ROUND_HALF_UP: 4;
static readonly ROUND_HALF_DOWN: 5;
static readonly ROUND_HALF_EVEN: 6;
static readonly ROUND_HALF_CEIL: 7;
static readonly ROUND_HALF_FLOOR: 8;
static readonly EUCLID: 9;
}
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
[K in keyof A]: Exact<A[K], W[K]>;
} : W) : never) | (A extends Narrowable ? A : never);
export declare function getRuntime(): GetRuntimeOutput;
declare type GetRuntimeOutput = {
id: RuntimeName;
prettyName: string;
isEdge: boolean;
};
declare class JsonNull extends NullTypesEnumValue {
#private;
}
/**
* Generates more strict variant of an enum which, unlike regular enum,
* throws on non-existing property access. This can be useful in following situations:
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
* - enum values are generated dynamically from DMMF.
*
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
* will result in `undefined` value being used, which will be accepted. Using strict enum
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
*
* Note: if you need to check for existence of a value in the enum you can still use either
* `in` operator or `hasOwnProperty` function.
*
* @param definition
* @returns
*/
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
declare type Narrowable = string | number | bigint | boolean | [];
declare class NullTypesEnumValue extends ObjectEnumValue {
_getNamespace(): string;
}
/**
* Base class for unique values of object-valued enums.
*/
declare abstract class ObjectEnumValue {
constructor(arg?: symbol);
abstract _getNamespace(): string;
_getName(): string;
toString(): string;
}
export declare const objectEnumValues: {
classes: {
DbNull: typeof DbNull;
JsonNull: typeof JsonNull;
AnyNull: typeof AnyNull;
};
instances: {
DbNull: DbNull;
JsonNull: JsonNull;
AnyNull: AnyNull;
};
};
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
declare namespace Public {
export {
validator
}
}
export { Public }
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
export { }
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,68 +0,0 @@
generator launcherClient {
provider = "prisma-client-js"
output = "../src/generated/launcher-client"
}
datasource db {
provider = "postgresql"
url = env("LAUNCHER_DATABASE_URL")
}
model LauncherNotice {
id String @id @default(uuid()) @db.Uuid
appKey String @default("login") @map("app_key")
title String
body String
linkUrl String? @map("link_url")
isActive Boolean @default(true) @map("is_active")
startsAt DateTime? @map("starts_at") @db.Timestamptz(3)
endsAt DateTime? @map("ends_at") @db.Timestamptz(3)
priority Int @default(0)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
@@index([appKey, isActive, priority])
@@map("launcher_notices")
}
model ProvisionedDevice {
id String @id @default(uuid()) @db.Uuid
accountEmail String @map("account_email")
authUserId String? @map("auth_user_id") @db.Uuid
hostname String
tailscaleHostname String @map("tailscale_hostname")
sshUser String? @map("ssh_user")
workspaceRoot String? @map("workspace_root")
usageType String @default("personal") @map("usage_type")
os String?
arch String?
kernel String?
distro String?
device Json?
installedAt DateTime @default(now()) @map("installed_at") @db.Timestamptz(3)
lastProvisionedAt DateTime @default(now()) @map("last_provisioned_at") @db.Timestamptz(3)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
@@unique([accountEmail, tailscaleHostname])
@@index([accountEmail])
@@index([tailscaleHostname])
@@map("provisioned_devices")
}
model LauncherSshKey {
id String @id @default(uuid()) @db.Uuid
accountEmail String @map("account_email")
authUserId String? @map("auth_user_id") @db.Uuid
publicKey String @map("public_key")
publicKeyHash String @map("public_key_hash")
keyType String? @map("key_type")
label String?
lastProvisionedAt DateTime @default(now()) @map("last_provisioned_at") @db.Timestamptz(3)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
@@unique([accountEmail, publicKeyHash])
@@index([accountEmail])
@@map("launcher_ssh_keys")
}
@@ -1,5 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
export default import('./query_engine_bg.wasm?module')
@@ -1,5 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
export default import('./query_engine_bg.wasm')
-1
View File
@@ -1 +0,0 @@
export * from "./default"
-254
View File
@@ -1,254 +0,0 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
Object.defineProperty(exports, "__esModule", { value: true });
const {
PrismaClientKnownRequestError,
PrismaClientUnknownRequestError,
PrismaClientRustPanicError,
PrismaClientInitializationError,
PrismaClientValidationError,
getPrismaClient,
sqltag,
empty,
join,
raw,
skip,
Decimal,
Debug,
objectEnumValues,
makeStrictEnum,
Extensions,
warnOnce,
defineDmmfProperty,
Public,
getRuntime,
createParam,
} = require('./runtime/wasm-engine-edge.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.19.3
* Query Engine version: c2990dca591cba766e3b7ef5d9e8a84796e47ab7
*/
Prisma.prismaVersion = {
client: "6.19.3",
engine: "c2990dca591cba766e3b7ef5d9e8a84796e47ab7"
}
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
Prisma.PrismaClientUnknownRequestError = PrismaClientUnknownRequestError
Prisma.PrismaClientRustPanicError = PrismaClientRustPanicError
Prisma.PrismaClientInitializationError = PrismaClientInitializationError
Prisma.PrismaClientValidationError = PrismaClientValidationError
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = sqltag
Prisma.empty = empty
Prisma.join = join
Prisma.raw = raw
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = Extensions.getExtensionContext
Prisma.defineExtension = Extensions.defineExtension
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = objectEnumValues.instances.DbNull
Prisma.JsonNull = objectEnumValues.instances.JsonNull
Prisma.AnyNull = objectEnumValues.instances.AnyNull
Prisma.NullTypes = {
DbNull: objectEnumValues.classes.DbNull,
JsonNull: objectEnumValues.classes.JsonNull,
AnyNull: objectEnumValues.classes.AnyNull
}
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
});
exports.Prisma.LauncherNoticeScalarFieldEnum = {
id: 'id',
appKey: 'appKey',
title: 'title',
body: 'body',
linkUrl: 'linkUrl',
isActive: 'isActive',
startsAt: 'startsAt',
endsAt: 'endsAt',
priority: 'priority',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.ProvisionedDeviceScalarFieldEnum = {
id: 'id',
accountEmail: 'accountEmail',
authUserId: 'authUserId',
hostname: 'hostname',
tailscaleHostname: 'tailscaleHostname',
sshUser: 'sshUser',
workspaceRoot: 'workspaceRoot',
usageType: 'usageType',
os: 'os',
arch: 'arch',
kernel: 'kernel',
distro: 'distro',
device: 'device',
installedAt: 'installedAt',
lastProvisionedAt: 'lastProvisionedAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.LauncherSshKeyScalarFieldEnum = {
id: 'id',
accountEmail: 'accountEmail',
authUserId: 'authUserId',
publicKey: 'publicKey',
publicKeyHash: 'publicKeyHash',
keyType: 'keyType',
label: 'label',
lastProvisionedAt: 'lastProvisionedAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.NullableJsonNullValueInput = {
DbNull: Prisma.DbNull,
JsonNull: Prisma.JsonNull
};
exports.Prisma.QueryMode = {
default: 'default',
insensitive: 'insensitive'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.Prisma.JsonNullValueFilter = {
DbNull: Prisma.DbNull,
JsonNull: Prisma.JsonNull,
AnyNull: Prisma.AnyNull
};
exports.Prisma.ModelName = {
LauncherNotice: 'LauncherNotice',
ProvisionedDevice: 'ProvisionedDevice',
LauncherSshKey: 'LauncherSshKey'
};
/**
* Create the Client
*/
const config = {
"generator": {
"name": "launcherClient",
"provider": {
"fromEnvVar": null,
"value": "prisma-client-js"
},
"output": {
"value": "/home/chyoo/tmp/atnmy-stdo-v2/atnmy-stdo-adm-v2-be/src/generated/launcher-client",
"fromEnvVar": null
},
"config": {
"engineType": "library"
},
"binaryTargets": [
{
"fromEnvVar": null,
"value": "debian-openssl-3.0.x",
"native": true
}
],
"previewFeatures": [],
"sourceFilePath": "/home/chyoo/tmp/atnmy-stdo-v2/atnmy-stdo-adm-v2-be/prisma/launcher.schema.prisma",
"isCustomOutput": true
},
"relativeEnvPaths": {
"rootEnvPath": null,
"schemaEnvPath": "../../../.env"
},
"relativePath": "../../../prisma",
"clientVersion": "6.19.3",
"engineVersion": "c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
"datasourceNames": [
"db"
],
"activeProvider": "postgresql",
"postinstall": false,
"inlineDatasources": {
"db": {
"url": {
"fromEnvVar": "LAUNCHER_DATABASE_URL",
"value": null
}
}
},
"inlineSchema": "generator launcherClient {\n provider = \"prisma-client-js\"\n output = \"../src/generated/launcher-client\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"LAUNCHER_DATABASE_URL\")\n}\n\nmodel LauncherNotice {\n id String @id @default(uuid()) @db.Uuid\n appKey String @default(\"login\") @map(\"app_key\")\n title String\n body String\n linkUrl String? @map(\"link_url\")\n isActive Boolean @default(true) @map(\"is_active\")\n startsAt DateTime? @map(\"starts_at\") @db.Timestamptz(3)\n endsAt DateTime? @map(\"ends_at\") @db.Timestamptz(3)\n priority Int @default(0)\n createdAt DateTime @default(now()) @map(\"created_at\") @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @map(\"updated_at\") @db.Timestamptz(3)\n\n @@index([appKey, isActive, priority])\n @@map(\"launcher_notices\")\n}\n\nmodel ProvisionedDevice {\n id String @id @default(uuid()) @db.Uuid\n accountEmail String @map(\"account_email\")\n authUserId String? @map(\"auth_user_id\") @db.Uuid\n hostname String\n tailscaleHostname String @map(\"tailscale_hostname\")\n sshUser String? @map(\"ssh_user\")\n workspaceRoot String? @map(\"workspace_root\")\n usageType String @default(\"personal\") @map(\"usage_type\")\n os String?\n arch String?\n kernel String?\n distro String?\n device Json?\n installedAt DateTime @default(now()) @map(\"installed_at\") @db.Timestamptz(3)\n lastProvisionedAt DateTime @default(now()) @map(\"last_provisioned_at\") @db.Timestamptz(3)\n createdAt DateTime @default(now()) @map(\"created_at\") @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @map(\"updated_at\") @db.Timestamptz(3)\n\n @@unique([accountEmail, tailscaleHostname])\n @@index([accountEmail])\n @@index([tailscaleHostname])\n @@map(\"provisioned_devices\")\n}\n\nmodel LauncherSshKey {\n id String @id @default(uuid()) @db.Uuid\n accountEmail String @map(\"account_email\")\n authUserId String? @map(\"auth_user_id\") @db.Uuid\n publicKey String @map(\"public_key\")\n publicKeyHash String @map(\"public_key_hash\")\n keyType String? @map(\"key_type\")\n label String?\n lastProvisionedAt DateTime @default(now()) @map(\"last_provisioned_at\") @db.Timestamptz(3)\n createdAt DateTime @default(now()) @map(\"created_at\") @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @map(\"updated_at\") @db.Timestamptz(3)\n\n @@unique([accountEmail, publicKeyHash])\n @@index([accountEmail])\n @@map(\"launcher_ssh_keys\")\n}\n",
"inlineSchemaHash": "ad20160732d203c5e1ea82cbcac0488ce94e47432c3136a3833df4661c0a049b",
"copyEngine": true
}
config.dirname = '/'
config.runtimeDataModel = JSON.parse("{\"models\":{\"LauncherNotice\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appKey\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"app_key\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"body\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"linkUrl\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"link_url\"},{\"name\":\"isActive\",\"kind\":\"scalar\",\"type\":\"Boolean\",\"dbName\":\"is_active\"},{\"name\":\"startsAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"starts_at\"},{\"name\":\"endsAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"ends_at\"},{\"name\":\"priority\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"}],\"dbName\":\"launcher_notices\"},\"ProvisionedDevice\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accountEmail\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"account_email\"},{\"name\":\"authUserId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"auth_user_id\"},{\"name\":\"hostname\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"tailscaleHostname\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"tailscale_hostname\"},{\"name\":\"sshUser\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"ssh_user\"},{\"name\":\"workspaceRoot\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"workspace_root\"},{\"name\":\"usageType\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"usage_type\"},{\"name\":\"os\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"arch\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"kernel\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"distro\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"device\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"installedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"installed_at\"},{\"name\":\"lastProvisionedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"last_provisioned_at\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"}],\"dbName\":\"provisioned_devices\"},\"LauncherSshKey\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accountEmail\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"account_email\"},{\"name\":\"authUserId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"auth_user_id\"},{\"name\":\"publicKey\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"public_key\"},{\"name\":\"publicKeyHash\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"public_key_hash\"},{\"name\":\"keyType\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"key_type\"},{\"name\":\"label\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastProvisionedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"last_provisioned_at\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"}],\"dbName\":\"launcher_ssh_keys\"}},\"enums\":{},\"types\":{}}")
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
config.engineWasm = {
getRuntime: async () => require('./query_engine_bg.js'),
getQueryEngineWasmModule: async () => {
const loader = (await import('#wasm-engine-loader')).default
const engine = (await loader).default
return engine
}
}
config.compilerWasm = undefined
config.injectableEdgeEnv = () => ({
parsed: {
LAUNCHER_DATABASE_URL: typeof globalThis !== 'undefined' && globalThis['LAUNCHER_DATABASE_URL'] || typeof process !== 'undefined' && process.env && process.env.LAUNCHER_DATABASE_URL || undefined
}
})
if (typeof globalThis !== 'undefined' && globalThis['DEBUG'] || typeof process !== 'undefined' && process.env && process.env.DEBUG || undefined) {
Debug.enable(typeof globalThis !== 'undefined' && globalThis['DEBUG'] || typeof process !== 'undefined' && process.env && process.env.DEBUG || undefined)
}
const PrismaClient = getPrismaClient(config)
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)
+67
View File
@@ -1,4 +1,5 @@
import {
BadRequestException,
BadGatewayException,
Injectable,
UnauthorizedException,
@@ -118,6 +119,10 @@ export class GiteaService {
params.set('page', String(page));
if (query.sha) {
params.set('sha', query.sha);
const { branches } = await this.listBranches(owner, repo) as { branches: Array<{ name?: string }> };
if (!branches.some((branch) => branch.name === query.sha)) {
throw new BadRequestException('해당 브랜치가 없습니다.');
}
}
if (query.since) {
params.set('since', query.since);
@@ -237,6 +242,68 @@ export class GiteaService {
);
}
async createUser(body: { username: string; email: string; password: string; fullName?: string }) {
this.assertToken();
await this.request(
'/api/v1/admin/users',
{
method: 'POST',
body: JSON.stringify({
username: body.username,
email: body.email,
password: body.password,
full_name: body.fullName,
must_change_password: false,
send_notify: false,
}),
allowedStatuses: [409, 422],
},
);
}
async updateUserLoginDisabled(username: string, disabled: boolean) {
this.assertToken();
await this.request(
`/api/v1/admin/users/${encodeURIComponent(username)}`,
{
method: 'PATCH',
body: JSON.stringify({
login_name: username,
prohibit_login: disabled,
}),
allowedStatuses: [404],
},
);
}
async updateUserAvatar(username: string, imageData: Buffer, mimeType: string) {
this.assertToken();
await this.request(
`/api/v1/user/avatar?sudo=${encodeURIComponent(username)}`,
{
method: 'POST',
body: JSON.stringify({
image: imageData.toString('base64'),
}),
},
);
}
async deleteUserAvatar(username: string) {
this.assertToken();
await this.request(
`/api/v1/user/avatar?sudo=${encodeURIComponent(username)}`,
{
method: 'DELETE',
allowedStatuses: [404],
},
);
}
async deleteUserSshKey(username: string, keyId: number) {
this.assertToken();
+28 -56
View File
@@ -71,9 +71,37 @@ export class LinkLauncherVersionSourceDto {
}
export class DeploymentResourceLimitsDto {
@IsOptional()
@Type(() => Number)
@IsInt()
replicas?: number;
@IsOptional()
@IsString()
cpuRequest?: string;
@IsOptional()
@IsString()
cpuLimit?: string;
@IsOptional()
@IsString()
memoryRequest?: string;
@IsOptional()
@IsString()
memoryLimit?: string;
}
export class DeployLauncherBackendDto {
@IsString()
image!: string;
@IsOptional()
@ValidateNested()
@Type(() => DeploymentResourceLimitsDto)
resources?: DeploymentResourceLimitsDto;
}
export class CreateLauncherBuildJobDto {
@@ -279,59 +307,3 @@ export class UpdateLauncherBuildEnvDto {
@Type(() => LauncherBuildEnvVariableDto)
variables!: LauncherBuildEnvVariableDto[];
}
export class CreateLauncherCommonSettingDto {
@IsString()
key!: string;
@IsString()
name!: string;
@IsString()
type!: string;
@IsString()
value!: string;
@IsOptional()
@IsString()
description?: string;
@IsOptional()
@IsBoolean()
isActive?: boolean;
@IsOptional()
@IsBoolean()
isSecret?: boolean;
}
export class UpdateLauncherCommonSettingDto {
@IsOptional()
@IsString()
key?: string;
@IsOptional()
@IsString()
name?: string;
@IsOptional()
@IsString()
type?: string;
@IsOptional()
@IsString()
value?: string;
@IsOptional()
@IsString()
description?: string;
@IsOptional()
@IsBoolean()
isActive?: boolean;
@IsOptional()
@IsBoolean()
isSecret?: boolean;
}
+6 -28
View File
@@ -1,6 +1,6 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, Req } from '@nestjs/common';
import { CreateLauncherAppLinkDto, CreateLauncherBuildJobDto, CreateLauncherCommonSettingDto, CreateLauncherVersionDto, UpdateLauncherSourceDto, LinkLauncherVersionSourceDto, MoveLauncherAppLinkDto, UpdateLauncherAppLinkDto, UpdateLauncherBuildEnvDto, UpdateLauncherCommonSettingDto, UpdateLauncherUserAppPermissionsDto, DeployLauncherBackendDto, ListLauncherBuildJobsQuery } from './dto';
import { CreateLauncherAppLinkDto, CreateLauncherBuildJobDto, CreateLauncherVersionDto, UpdateLauncherSourceDto, LinkLauncherVersionSourceDto, MoveLauncherAppLinkDto, UpdateLauncherAppLinkDto, UpdateLauncherBuildEnvDto, UpdateLauncherUserAppPermissionsDto, DeployLauncherBackendDto, ListLauncherBuildJobsQuery } from './dto';
import { LauncherService } from './launcher.service';
@Controller('launcher')
@@ -54,33 +54,11 @@ export class LauncherController {
}
@Post('builds')
createBuildJob(@Body() body: CreateLauncherBuildJobDto) {
return this.launcherService.createBuildJob(body);
}
@Get('common-settings')
listCommonSettings() {
return this.launcherService.listCommonSettings();
}
@Post('common-settings')
createCommonSetting(@Body() body: CreateLauncherCommonSettingDto) {
return this.launcherService.createCommonSetting(body);
}
@Patch('common-settings/:id')
updateCommonSetting(
@Param('id') id: string,
@Body() body: UpdateLauncherCommonSettingDto,
createBuildJob(
@Body() body: CreateLauncherBuildJobDto,
@Req() request: { admin?: { id: string; name: string; email: string } },
) {
return this.launcherService.updateCommonSetting(id, body);
}
@Delete('common-settings/:id')
deleteCommonSetting(@Param('id') id: string) {
return this.launcherService.deleteCommonSetting(id);
return this.launcherService.createBuildJob(body, request.admin);
}
@Get('app-links')
+79 -67
View File
@@ -10,10 +10,10 @@ import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { LauncherPrismaService } from '../launcher-prisma/launcher-prisma.service';
import { PrismaService } from '../prisma/prisma.service';
import { getAdminAvatarUrl } from '../admins/admin-avatar';
import {
CreateLauncherAppLinkDto,
CreateLauncherBuildJobDto,
CreateLauncherCommonSettingDto,
CreateLauncherVersionDto,
DeployLauncherBackendDto,
ListLauncherBuildJobsQuery,
@@ -21,7 +21,6 @@ import {
LinkLauncherVersionSourceDto,
UpdateLauncherAppLinkDto,
UpdateLauncherBuildEnvDto,
UpdateLauncherCommonSettingDto,
UpdateLauncherUserAppPermissionsDto,
} from './dto';
@@ -76,51 +75,6 @@ export class LauncherService {
return rows.map(mapLauncherVersionRow);
}
listCommonSettings() {
return this.launcherPrisma.launcherCommonSetting.findMany({
orderBy: [{ isActive: 'desc' }, { type: 'asc' }, { name: 'asc' }],
});
}
async createCommonSetting(body: CreateLauncherCommonSettingDto) {
const setting = await this.launcherPrisma.launcherCommonSetting.create({
data: {
key: body.key.trim(),
name: body.name.trim(),
type: body.type.trim(),
value: body.value.trim(),
description: body.description?.trim() || null,
isActive: body.isActive ?? true,
isSecret: body.isSecret ?? false,
},
});
return { setting };
}
async updateCommonSetting(id: string, body: UpdateLauncherCommonSettingDto) {
const setting = await this.launcherPrisma.launcherCommonSetting.update({
where: { id },
data: {
key: body.key === undefined ? undefined : body.key.trim(),
name: body.name === undefined ? undefined : body.name.trim(),
type: body.type === undefined ? undefined : body.type.trim(),
value: body.value === undefined ? undefined : body.value.trim(),
description: body.description === undefined ? undefined : body.description.trim() || null,
isActive: body.isActive,
isSecret: body.isSecret,
},
});
return { setting };
}
async deleteCommonSetting(id: string) {
await this.launcherPrisma.launcherCommonSetting.delete({ where: { id } });
return { deleted: true };
}
async getSource() {
const source = await this.getSourceSettings();
return { source };
@@ -136,35 +90,35 @@ export class LauncherService {
const dockerfile = body.dockerfile?.trim() || 'Dockerfile';
const writes = isBackend ? [
this.upsertCommonSettingByKey(
this.upsertLauncherSettingByKey(
LAUNCHER_SOURCE_SETTING_KEYS.backendRepository,
'런처 백엔드 소스 저장소',
'source',
repository,
'런처 백엔드 컨테이너 빌드에 사용할 저장소',
),
this.upsertCommonSettingByKey(
this.upsertLauncherSettingByKey(
LAUNCHER_SOURCE_SETTING_KEYS.backendBranch,
'런처 백엔드 소스 브랜치',
'source',
branch,
'런처 백엔드 컨테이너 빌드 기준 브랜치',
),
this.upsertCommonSettingByKey(
this.upsertLauncherSettingByKey(
LAUNCHER_SOURCE_SETTING_KEYS.backendCommitSha,
'런처 백엔드 소스 커밋',
'source',
commitSha,
'비워두면 브랜치 최신 커밋 기준',
),
this.upsertCommonSettingByKey(
this.upsertLauncherSettingByKey(
LAUNCHER_SOURCE_SETTING_KEYS.backendImage,
'런처 백엔드 이미지',
'source',
image,
'런처 백엔드 컨테이너 이미지 이름',
),
this.upsertCommonSettingByKey(
this.upsertLauncherSettingByKey(
LAUNCHER_SOURCE_SETTING_KEYS.backendDockerfile,
'런처 백엔드 Dockerfile',
'source',
@@ -172,21 +126,21 @@ export class LauncherService {
'런처 백엔드 빌드에 사용할 Dockerfile 경로',
),
] : [
this.upsertCommonSettingByKey(
this.upsertLauncherSettingByKey(
LAUNCHER_SOURCE_SETTING_KEYS.repository,
'런처 소스 저장소',
'source',
repository,
'런처 클라이언트 빌드에 사용할 저장소',
),
this.upsertCommonSettingByKey(
this.upsertLauncherSettingByKey(
LAUNCHER_SOURCE_SETTING_KEYS.branch,
'런처 소스 브랜치',
'source',
branch,
'런처 클라이언트 빌드 기준 브랜치',
),
this.upsertCommonSettingByKey(
this.upsertLauncherSettingByKey(
LAUNCHER_SOURCE_SETTING_KEYS.commitSha,
'런처 소스 커밋',
'source',
@@ -209,7 +163,7 @@ export class LauncherService {
}
private async getSourceSettings() {
const rows = await this.launcherPrisma.launcherCommonSetting.findMany({
const rows = await this.launcherPrisma.launcherSetting.findMany({
where: {
key: {
in: [
@@ -256,7 +210,7 @@ export class LauncherService {
private async getLauncherClientBuildEnv() {
const rows = await this.launcherPrisma.launcherCommonSetting.findMany({
const rows = await this.launcherPrisma.launcherSetting.findMany({
where: {
key: {
startsWith: LAUNCHER_BUILD_ENV_PREFIX,
@@ -283,7 +237,7 @@ export class LauncherService {
.filter((row) => row.key);
}
private upsertCommonSettingByKey(
private upsertLauncherSettingByKey(
key: string,
name: string,
type: string,
@@ -291,7 +245,7 @@ export class LauncherService {
description: string,
isSecret = false,
) {
return this.launcherPrisma.launcherCommonSetting.upsert({
return this.launcherPrisma.launcherSetting.upsert({
where: { key },
create: {
key,
@@ -322,7 +276,7 @@ export class LauncherService {
async updateBuildEnv(body: UpdateLauncherBuildEnvDto) {
const variables = normalizeBuildEnvVariables(body.variables ?? []);
const nextKeys = new Set(variables.map((variable) => variable.key));
const existing = await this.launcherPrisma.launcherCommonSetting.findMany({
const existing = await this.launcherPrisma.launcherSetting.findMany({
where: {
key: {
startsWith: LAUNCHER_BUILD_ENV_PREFIX,
@@ -333,8 +287,8 @@ export class LauncherService {
await this.launcherPrisma.$transaction([
...existing
.filter((setting) => !nextKeys.has(setting.key.slice(LAUNCHER_BUILD_ENV_PREFIX.length)))
.map((setting) => this.launcherPrisma.launcherCommonSetting.delete({ where: { id: setting.id } })),
...variables.map((variable) => this.upsertCommonSettingByKey(
.map((setting) => this.launcherPrisma.launcherSetting.delete({ where: { id: setting.id } })),
...variables.map((variable) => this.upsertLauncherSettingByKey(
`${LAUNCHER_BUILD_ENV_PREFIX}${variable.key}`,
variable.key,
'launcher-build-env',
@@ -406,6 +360,43 @@ export class LauncherService {
'-n',
namespace,
]));
const resources = body.resources;
if (resources?.replicas && resources.replicas > 0) {
logs.push(`[scale] deployment/${deployment} replicas=${resources.replicas}`);
appendOutput(logs, await this.kubectl([
'scale',
`deployment/${deployment}`,
`--replicas=${resources.replicas}`,
'-n',
namespace,
]));
}
const requests = [
resources?.cpuRequest ? `cpu=${resources.cpuRequest}` : null,
resources?.memoryRequest ? `memory=${resources.memoryRequest}` : null,
].filter(Boolean);
const limits = [
resources?.cpuLimit ? `cpu=${resources.cpuLimit}` : null,
resources?.memoryLimit ? `memory=${resources.memoryLimit}` : null,
].filter(Boolean);
if (requests.length || limits.length) {
const args = [
'set',
'resources',
`deployment/${deployment}`,
`--containers=${container}`,
...(requests.length ? [`--requests=${requests.join(',')}`] : []),
...(limits.length ? [`--limits=${limits.join(',')}`] : []),
'-n',
namespace,
];
logs.push(`[resources] ${args.slice(3, -2).join(' ')}`);
appendOutput(logs, await this.kubectl(args));
}
logs.push(`[rollout] deployment/${deployment}`);
appendOutput(logs, await this.kubectl([
'rollout',
@@ -427,7 +418,7 @@ export class LauncherService {
};
}
listBuildJobs(query: ListLauncherBuildJobsQuery = {}) {
async listBuildJobs(query: ListLauncherBuildJobsQuery = {}) {
const paged = Boolean(query.page || query.limit || query.target || query.since);
const page = toPositiveInt(query.page, 1);
const limit = Math.min(toPositiveInt(query.limit, 30), 100);
@@ -438,10 +429,11 @@ export class LauncherService {
};
if (!paged) {
return this.prisma.launcherBuildJob.findMany({
const jobs = await this.prisma.launcherBuildJob.findMany({
orderBy: { createdAt: 'desc' },
take: 30,
});
return this.withLauncherBuildRequesters(jobs);
}
return this.prisma.$transaction(async (tx) => {
@@ -455,10 +447,17 @@ export class LauncherService {
tx.launcherBuildJob.count({ where }),
]);
return { jobs, total, page, limit };
return { jobs: await this.withLauncherBuildRequesters(jobs), total, page, limit };
});
}
private withLauncherBuildRequesters<T extends { requestedById?: string | null }>(jobs: T[]) {
return jobs.map((job) => ({
...job,
requestedByAvatarUrl: getAdminAvatarUrl(job.requestedById, this.configService),
}));
}
async listAppLinks() {
const [launcherRows, adminApps] = await Promise.all([
@@ -501,6 +500,13 @@ export class LauncherService {
};
}
async listEnabledAppKeys() {
const rows = await this.launcherPrisma.$queryRawUnsafe<Array<{ app_key: string }>>(
`SELECT app_key FROM launcher_apps WHERE is_enabled = true`,
);
return rows.map((row) => row.app_key);
}
async listUserAppPermissions(userId: string) {
const rows = await this.launcherPrisma.$queryRawUnsafe<LauncherUserAppPermissionRow[]>(
`SELECT app.id AS app_id, app.app_key, app.display_name, app.description, app.sort_order, app.is_enabled,
@@ -953,7 +959,10 @@ export class LauncherService {
return { version: mapLauncherVersionRow(rows[0]) };
}
async createBuildJob(body: CreateLauncherBuildJobDto) {
async createBuildJob(
body: CreateLauncherBuildJobDto,
requester?: { id: string; name: string; email: string },
) {
const source = await this.getSourceSettings();
const target = body.target?.trim() || 'launcher-client';
const isBackend = target === 'launcher-backend';
@@ -997,12 +1006,15 @@ export class LauncherService {
artifact: requestedArtifact,
uploadPath: plan.uploadPath,
downloadUrl: plan.downloadUrl,
requestedById: requester?.id,
requestedByName: requester?.name,
requestedByEmail: requester?.email,
logs,
},
});
void this.runBuildJob(job.id);
return { job };
return { job: (await this.withLauncherBuildRequesters([job]))[0] };
}
async linkVersionSource(id: string, body: LinkLauncherVersionSourceDto) {
+55 -1
View File
@@ -1,8 +1,17 @@
import { IsEnum, IsISO8601, IsOptional, IsString } from 'class-validator';
import { Type } from 'class-transformer';
import { IsArray, IsBoolean, IsEnum, IsInt, IsISO8601, IsOptional, IsString, Min, ValidateNested } from 'class-validator';
import { LicenseCode } from './license-enums';
export class ListUserLicensesQuery {
@IsOptional()
@IsString()
page?: string;
@IsOptional()
@IsString()
limit?: string;
@IsOptional()
@IsString()
keyword?: string;
@@ -10,6 +19,10 @@ export class ListUserLicensesQuery {
@IsOptional()
@IsEnum(LicenseCode)
licenseCode?: LicenseCode;
@IsOptional()
@IsString()
status?: string;
}
export class AssignLicenseDto {
@@ -28,3 +41,44 @@ export class AssignLicenseDto {
@IsString()
assignedById?: string;
}
export class UpdateLicenseAppPermissionDto {
@IsString()
appKey!: string;
@IsBoolean()
canLaunch!: boolean;
}
export class UpdateLicenseAppPermissionsDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => UpdateLicenseAppPermissionDto)
permissions!: UpdateLicenseAppPermissionDto[];
}
export class UpdateLicensePlanDto {
@IsOptional()
@IsString()
name?: string;
@IsOptional()
@IsString()
description?: string | null;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
defaultDurationDays?: number | null;
@IsOptional()
@IsBoolean()
isActive?: boolean;
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => UpdateLicenseAppPermissionDto)
permissions?: UpdateLicenseAppPermissionDto[];
}
+34 -6
View File
@@ -1,6 +1,6 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, Req } from '@nestjs/common';
import { AssignLicenseDto, ListUserLicensesQuery } from './dto';
import { AssignLicenseDto, ListUserLicensesQuery, UpdateLicenseAppPermissionsDto, UpdateLicensePlanDto } from './dto';
import { LicensesService } from './licenses.service';
@Controller('licenses')
@@ -12,19 +12,47 @@ export class LicensesController {
return this.licensesService.listPlans();
}
@Get('plans/:code/app-permissions')
listPlanAppPermissions(@Param('code') code: string) {
return this.licensesService.listPlanAppPermissions(code);
}
@Put('plans/:code/app-permissions')
updatePlanAppPermissions(@Param('code') code: string, @Body() body: UpdateLicenseAppPermissionsDto) {
return this.licensesService.updatePlanAppPermissions(code, body);
}
@Patch('plans/:code')
updatePlan(@Param('code') code: string, @Body() body: UpdateLicensePlanDto) {
return this.licensesService.updatePlan(code, body);
}
@Get('users')
listUserLicenses(@Query() query: ListUserLicensesQuery) {
return this.licensesService.listUserLicenses(query);
}
@Get('assignments')
listAssignments(@Query() query: ListUserLicensesQuery) {
return this.licensesService.listAssignments(query);
}
@Post('users/:userId')
assign(@Param('userId') userId: string, @Body() body: AssignLicenseDto) {
return this.licensesService.assign(userId, body);
assign(
@Param('userId') userId: string,
@Body() body: AssignLicenseDto,
@Req() request: { admin?: { id: string } },
) {
return this.licensesService.assign(userId, body, request.admin?.id);
}
@Patch('user-licenses/:id')
update(@Param('id') id: string, @Body() body: AssignLicenseDto) {
return this.licensesService.update(id, body);
update(
@Param('id') id: string,
@Body() body: AssignLicenseDto,
@Req() request: { admin?: { id: string } },
) {
return this.licensesService.update(id, body, request.admin?.id);
}
@Delete('user-licenses/:id')

Some files were not shown because too many files have changed in this diff Show More