diff --git a/appwrite-functions/.gitkeep b/appwrite-functions/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/appwrite-functions/check-cloudflare-status/function.json b/appwrite-functions/check-cloudflare-status/function.json deleted file mode 100644 index 9b52026..0000000 --- a/appwrite-functions/check-cloudflare-status/function.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "https://appwrite.io/docs/schemas/functions.json", - "name": "check-cloudflare-status", - "entrypoint": "src/index.js", - "runtime": "node-20.0", - "commands": ["npm install"], - "ignore": ["node_modules", ".npm", "npm-debug.log", "build"] -} diff --git a/appwrite-functions/check-cloudflare-status/package-lock.json b/appwrite-functions/check-cloudflare-status/package-lock.json deleted file mode 100644 index a7e6739..0000000 --- a/appwrite-functions/check-cloudflare-status/package-lock.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "check-cloudflare-status", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "check-cloudflare-status", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "node-appwrite": "^14.0.0" - } - }, - "node_modules/node-appwrite": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-14.2.0.tgz", - "integrity": "sha512-sPPA+JzdBJRS+lM6azX85y3/6iyKQYlHcXCbjMuWLROh6IiU9EfXRW3XSUTa5HDoBrlo8ve+AnVA6BIjQfUs1g==", - "license": "BSD-3-Clause", - "dependencies": { - "node-fetch-native-with-agent": "1.7.2" - } - }, - "node_modules/node-fetch-native-with-agent": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-fetch-native-with-agent/-/node-fetch-native-with-agent-1.7.2.tgz", - "integrity": "sha512-5MaOOCuJEvcckoz7/tjdx1M6OusOY6Xc5f459IaruGStWnKzlI1qpNgaAwmn4LmFYcsSlj+jBMk84wmmRxfk5g==", - "license": "MIT" - } - } -} diff --git a/appwrite-functions/check-cloudflare-status/package.json b/appwrite-functions/check-cloudflare-status/package.json deleted file mode 100644 index 4d83fcf..0000000 --- a/appwrite-functions/check-cloudflare-status/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "check-cloudflare-status", - "version": "1.0.0", - "type": "module", - "main": "src/index.js", - "license": "MIT", - "dependencies": { - "node-appwrite": "^14.0.0" - } -} diff --git a/appwrite-functions/check-cloudflare-status/src/index.js b/appwrite-functions/check-cloudflare-status/src/index.js deleted file mode 100644 index d6a89d3..0000000 --- a/appwrite-functions/check-cloudflare-status/src/index.js +++ /dev/null @@ -1,108 +0,0 @@ -import { Client, Databases } from 'node-appwrite'; - -const APPWRITE_ENDPOINT = process.env.APPWRITE_ENDPOINT || process.env.APPWRITE_FUNCTION_ENDPOINT; -const APPWRITE_PROJECT_ID = process.env.APPWRITE_PROJECT_ID || process.env.APPWRITE_FUNCTION_PROJECT_ID; -const APPWRITE_API_KEY = process.env.APPWRITE_API_KEY; -const DATABASE_ID = process.env.APPWRITE_DATABASE_ID; - -const cfHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json' -}); - -async function fetchZones(token, log) { - const response = await fetch('https://api.cloudflare.com/client/v4/zones', { - headers: cfHeaders(token) - }); - - if (!response.ok) { - const body = await response.text(); - log(`Cloudflare zones error: ${body}`); - throw new Error('Failed to fetch Cloudflare zones'); - } - - const { result } = await response.json(); - return result.map((zone) => ({ - id: zone.id, - name: zone.name, - status: zone.status, - paused: zone.paused - })); -} - -async function fetchWorkers(token, accountId, log) { - if (!accountId) return []; - - const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/scripts`, { - headers: cfHeaders(token) - }); - - if (!response.ok) { - const body = await response.text(); - log(`Cloudflare workers error: ${body}`); - throw new Error('Failed to fetch Cloudflare workers'); - } - - const { result } = await response.json(); - return result.map((worker) => ({ - name: worker.name, - modifiedOn: worker.modified_on, - active: worker.created_on !== undefined - })); -} - -export default async function ({ req, res, log, error }) { - try { - if (!APPWRITE_ENDPOINT || !APPWRITE_PROJECT_ID || !APPWRITE_API_KEY || !DATABASE_ID) { - return res.json({ error: 'Missing Appwrite environment configuration.' }, 500); - } - - const payload = req.body ? JSON.parse(req.body) : {}; - const accountId = payload.accountId; - const requesterId = - (req.headers && (req.headers['x-appwrite-user-id'] || req.headers['x-appwrite-userid'])) || - process.env.APPWRITE_FUNCTION_USER_ID || - payload.userId; - - if (!accountId) { - return res.json({ error: 'accountId is required in the request body.' }, 400); - } - - const client = new Client() - .setEndpoint(APPWRITE_ENDPOINT) - .setProject(APPWRITE_PROJECT_ID) - .setKey(APPWRITE_API_KEY); - - const databases = new Databases(client); - const account = await databases.getDocument(DATABASE_ID, 'cloud_accounts', accountId); - - if (!account || account.provider !== 'cloudflare') { - return res.json({ error: 'Cloud account not found or not a Cloudflare credential.' }, 404); - } - - if (account.userId && requesterId && account.userId !== requesterId) { - return res.json({ error: 'You are not allowed to use this credential.' }, 403); - } - - const token = account.apiKey; - if (!token) { - return res.json({ error: 'Cloudflare token is missing for this account.' }, 400); - } - - const cloudflareAccountId = payload.cloudflareAccountId || account.cloudflareAccountId; - - const [zones, workers] = await Promise.all([ - fetchZones(token, log), - fetchWorkers(token, cloudflareAccountId, log) - ]); - - return res.json({ - zones, - workers, - message: workers.length ? 'Zones and Workers status fetched successfully.' : 'Zones status fetched successfully.' - }); - } catch (err) { - error(err.message); - return res.json({ error: 'Unexpected error while checking Cloudflare status.' }, 500); - } -} diff --git a/appwrite-functions/hello-world/function.json b/appwrite-functions/hello-world/function.json deleted file mode 100644 index 8996b6f..0000000 --- a/appwrite-functions/hello-world/function.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "https://appwrite.io/docs/schemas/functions.json", - "name": "hello-world", - "entrypoint": "src/index.js", - "runtime": "node-20.0", - "commands": ["npm install"], - "ignore": ["node_modules", ".npm", "npm-debug.log", "build"] -} diff --git a/appwrite-functions/hello-world/package-lock.json b/appwrite-functions/hello-world/package-lock.json deleted file mode 100644 index e6f267d..0000000 --- a/appwrite-functions/hello-world/package-lock.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "hello-world", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "hello-world", - "version": "1.0.0", - "license": "MIT" - } - } -} diff --git a/appwrite-functions/hello-world/package.json b/appwrite-functions/hello-world/package.json deleted file mode 100644 index 8c04632..0000000 --- a/appwrite-functions/hello-world/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "hello-world", - "version": "1.0.0", - "type": "module", - "main": "src/index.js", - "license": "MIT", - "scripts": {} -} diff --git a/appwrite-functions/hello-world/src/index.js b/appwrite-functions/hello-world/src/index.js deleted file mode 100644 index 7942d72..0000000 --- a/appwrite-functions/hello-world/src/index.js +++ /dev/null @@ -1,13 +0,0 @@ -export default async function ({ req, res, log }) { - const payload = req.body ? JSON.parse(req.body) : {}; - const name = payload.name?.trim() || 'Appwrite'; - - const message = `Hello, ${name}! Your function is deployed and responding.`; - log(`hello-world executed for ${name}`); - - return res.json({ - message, - inputName: name, - timestamp: new Date().toISOString() - }); -} diff --git a/appwrite-functions/sync-github/function.json b/appwrite-functions/sync-github/function.json deleted file mode 100644 index c4340ed..0000000 --- a/appwrite-functions/sync-github/function.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "https://appwrite.io/docs/schemas/functions.json", - "name": "sync-github", - "entrypoint": "src/index.js", - "runtime": "node-20.0", - "commands": ["npm install"], - "ignore": ["node_modules", ".npm", "npm-debug.log", "build"] -} diff --git a/appwrite-functions/sync-github/package-lock.json b/appwrite-functions/sync-github/package-lock.json deleted file mode 100644 index c3a7035..0000000 --- a/appwrite-functions/sync-github/package-lock.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "sync-github", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "sync-github", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "node-appwrite": "^14.0.0" - } - }, - "node_modules/node-appwrite": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-14.2.0.tgz", - "integrity": "sha512-sPPA+JzdBJRS+lM6azX85y3/6iyKQYlHcXCbjMuWLROh6IiU9EfXRW3XSUTa5HDoBrlo8ve+AnVA6BIjQfUs1g==", - "license": "BSD-3-Clause", - "dependencies": { - "node-fetch-native-with-agent": "1.7.2" - } - }, - "node_modules/node-fetch-native-with-agent": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-fetch-native-with-agent/-/node-fetch-native-with-agent-1.7.2.tgz", - "integrity": "sha512-5MaOOCuJEvcckoz7/tjdx1M6OusOY6Xc5f459IaruGStWnKzlI1qpNgaAwmn4LmFYcsSlj+jBMk84wmmRxfk5g==", - "license": "MIT" - } - } -} diff --git a/appwrite-functions/sync-github/package.json b/appwrite-functions/sync-github/package.json deleted file mode 100644 index 22ae47e..0000000 --- a/appwrite-functions/sync-github/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "sync-github", - "version": "1.0.0", - "type": "module", - "main": "src/index.js", - "license": "MIT", - "dependencies": { - "node-appwrite": "^14.0.0" - } -} diff --git a/appwrite-functions/sync-github/src/index.js b/appwrite-functions/sync-github/src/index.js deleted file mode 100644 index 83181f8..0000000 --- a/appwrite-functions/sync-github/src/index.js +++ /dev/null @@ -1,76 +0,0 @@ -import { Client, Databases } from 'node-appwrite'; - -const APPWRITE_ENDPOINT = process.env.APPWRITE_ENDPOINT || process.env.APPWRITE_FUNCTION_ENDPOINT; -const APPWRITE_PROJECT_ID = process.env.APPWRITE_PROJECT_ID || process.env.APPWRITE_FUNCTION_PROJECT_ID; -const APPWRITE_API_KEY = process.env.APPWRITE_API_KEY; -const DATABASE_ID = process.env.APPWRITE_DATABASE_ID; - -const githubHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - Accept: 'application/vnd.github+json', - 'User-Agent': 'appwrite-sync-github' -}); - -export default async function ({ req, res, log, error }) { - try { - if (!APPWRITE_ENDPOINT || !APPWRITE_PROJECT_ID || !APPWRITE_API_KEY || !DATABASE_ID) { - return res.json({ error: 'Missing Appwrite environment configuration.' }, 500); - } - - const payload = req.body ? JSON.parse(req.body) : {}; - const accountId = payload.accountId; - const requesterId = - (req.headers && (req.headers['x-appwrite-user-id'] || req.headers['x-appwrite-userid'])) || - process.env.APPWRITE_FUNCTION_USER_ID || - payload.userId; - if (!accountId) { - return res.json({ error: 'accountId is required in the request body.' }, 400); - } - - const client = new Client() - .setEndpoint(APPWRITE_ENDPOINT) - .setProject(APPWRITE_PROJECT_ID) - .setKey(APPWRITE_API_KEY); - - const databases = new Databases(client); - const account = await databases.getDocument(DATABASE_ID, 'cloud_accounts', accountId); - - if (!account || account.provider !== 'github') { - return res.json({ error: 'Cloud account not found or not a GitHub credential.' }, 404); - } - - if (account.userId && requesterId && account.userId !== requesterId) { - return res.json({ error: 'You are not allowed to use this credential.' }, 403); - } - - const token = account.apiKey; - if (!token) { - return res.json({ error: 'GitHub token is missing for this account.' }, 400); - } - - const githubResponse = await fetch('https://api.github.com/user/repos?per_page=100', { - headers: githubHeaders(token) - }); - - if (!githubResponse.ok) { - const body = await githubResponse.text(); - log(`GitHub API error: ${body}`); - return res.json({ error: 'Failed to fetch repositories from GitHub.' }, githubResponse.status); - } - - const repositories = await githubResponse.json(); - const simplified = repositories.map((repo) => ({ - id: repo.id, - name: repo.name, - fullName: repo.full_name, - private: repo.private, - url: repo.html_url, - defaultBranch: repo.default_branch - })); - - return res.json({ repositories: simplified }, 200); - } catch (err) { - error(err.message); - return res.json({ error: 'Unexpected error while syncing with GitHub.' }, 500); - } -} diff --git a/automation-jobs-core/.dockerignore b/automation-jobs-core/.dockerignore deleted file mode 100644 index 84893f8..0000000 --- a/automation-jobs-core/.dockerignore +++ /dev/null @@ -1,9 +0,0 @@ -.git -.env -.gitignore -Dockerfile.api -Dockerfile.worker -README.md -AUTOMATION-JOBS-CORE.md -migrations -*.log diff --git a/automation-jobs-core/.gitignore b/automation-jobs-core/.gitignore deleted file mode 100644 index e4e96e7..0000000 --- a/automation-jobs-core/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.env -*.log -.DS_Store -automation-jobs-api -automation-jobs-worker -coverage diff --git a/automation-jobs-core/AUTOMATION-JOBS-CORE.md b/automation-jobs-core/AUTOMATION-JOBS-CORE.md deleted file mode 100644 index daf2585..0000000 --- a/automation-jobs-core/AUTOMATION-JOBS-CORE.md +++ /dev/null @@ -1,93 +0,0 @@ -# AUTOMATION-JOBS-CORE - -Este serviço é responsável pela execução de automações, workflows de longa duração e jobs agendados, utilizando o poder do [Temporal](https://temporal.io/) para garantir confiabilidade e idempotência. - -## 📋 Visão Geral - -O projeto é dividido em três componentes principais que trabalham em conjunto para processar tarefas assíncronas: - -1. **API (HTTP)**: Ponto de entrada leve para iniciar workflows. -2. **Temporal Server**: O "cérebro" que orquestra o estado e o agendamento das tarefas. -3. **Workers (Go)**: Onde o código da lógica de negócio (workflows e activities) realmente é executado. - -### Arquitetura - -O diagrama abaixo ilustra como os componentes interagem: - -```mermaid -graph TD - Client[Cliente Externo/Frontend] -->|HTTP POST /jobs/run| API[API Service :8080] - API -->|gRPC StartWorkflow| Temporal[Temporal Service :7233] - - subgraph Temporal Cluster - Temporal - DB[(PostgreSQL)] - Temporal --> DB - end - - Worker[Go Worker] -->|Poll TaskQueue| Temporal - Worker -->|Execute Activity| Worker - Worker -->|Return Result| Temporal -``` - -## 🚀 Estrutura do Projeto - -Abaixo está o detalhamento de cada diretório e arquivo importante: - -| Caminho | Descrição | -| :--- | :--- | -| `cmd/api/` | Ponto de entrada (`main.go`) para o serviço da API. | -| `cmd/worker/` | Ponto de entrada (`main.go`) para o serviço do Worker. | -| `internal/` | Código compartilhado e lógica interna do aplicativo. | -| `temporal/` | Definições de Workflows e Activities do Temporal. | -| `Dockerfile.api` | Configuração de build otimizada para a API (Distroless). | -| `Dockerfile.worker` | Configuração de build otimizada para o Worker (Distroless). | -| `docker-compose.yml` | Orquestração local de todos os serviços. | - -## 🛠️ Tecnologias e Otimizações - -- **Linguagem**: Go 1.23+ -- **Orquestração**: Temporal.io -- **Containerização**: - - Images baseadas em `gcr.io/distroless/static`. - - Multi-stage builds para reduzir o tamanho final da imagem (~20MB). - - Execução como usuário `nonroot` para segurança aprimorada. - -## 💻 Como Executar - -O projeto é projetado para ser executado via Docker Compose para um ambiente de desenvolvimento completo. - -### Pré-requisitos -- Docker Engine -- Docker Compose - -### Passo a Passo - -1. **Inicie o ambiente:** - ```bash - docker-compose up --build - ``` - Isso irá subir: - - Temporal Server & Web UI (Porta `8088`) - - PostgreSQL (Persistência do Temporal) - - API Service (Porta `8080`) - - Worker Service - -2. **Dispare um Workflow de Teste:** - ```bash - curl -X POST http://localhost:8080/jobs/run - ``` - -3. **Monitore a Execução:** - Acesse a interface do Temporal para ver o progresso em tempo real: - [http://localhost:8088](http://localhost:8088) - -## 🔧 Detalhes dos Dockerfiles - -Os Dockerfiles foram refatorados para máxima eficiência: - -- **Builder Stage**: Usa `golang:1.23-alpine` para compilar o binário estático, removendo informações de debug (`-ldflags="-w -s"`). -- **Runtime Stage**: Usa `gcr.io/distroless/static:nonroot`, que contém apenas o mínimo necessário para rodar binários Go, sem shell ou gerenciador de pacotes, garantindo: - - ✅ **Segurança**: Menor superfície de ataque. - - ✅ **Tamanho**: Imagens extremamente leves. - - ✅ **Performance**: Bootrápido. diff --git a/automation-jobs-core/Dockerfile.api b/automation-jobs-core/Dockerfile.api deleted file mode 100644 index f388547..0000000 --- a/automation-jobs-core/Dockerfile.api +++ /dev/null @@ -1,26 +0,0 @@ -# Dockerfile.api -FROM docker.io/library/golang:1.23-alpine AS builder - -WORKDIR /app - -COPY go.mod go.sum ./ -RUN go mod download - -COPY . . - -# Build with optimization flags -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /app/api ./cmd/api - -# Use Google Disroless static image for minimal size and security -FROM gcr.io/distroless/static:nonroot - -WORKDIR /app - -COPY --from=builder /app/api . - -# Non-root user for security -USER nonroot:nonroot - -EXPOSE 8080 - -CMD ["./api"] diff --git a/automation-jobs-core/Dockerfile.worker b/automation-jobs-core/Dockerfile.worker deleted file mode 100644 index ed023f5..0000000 --- a/automation-jobs-core/Dockerfile.worker +++ /dev/null @@ -1,24 +0,0 @@ -# Dockerfile.worker -FROM docker.io/library/golang:1.23-alpine AS builder - -WORKDIR /app - -COPY go.mod go.sum ./ -RUN go mod download - -COPY . . - -# Build with optimization flags -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /app/worker ./cmd/worker - -# Use Google Disroless static image for minimal size and security -FROM gcr.io/distroless/static:nonroot - -WORKDIR /app - -COPY --from=builder /app/worker . - -# Non-root user for security -USER nonroot:nonroot - -CMD ["./worker"] diff --git a/automation-jobs-core/cmd/api/main.go b/automation-jobs-core/cmd/api/main.go deleted file mode 100644 index 371c018..0000000 --- a/automation-jobs-core/cmd/api/main.go +++ /dev/null @@ -1,136 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "log/slog" - "net/http" - "os" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "github.com/google/uuid" - "github.com/lab/automation-jobs-core/temporal/workflows" - "go.temporal.io/sdk/client" -) - -// The task queue name for our sample workflow. -const SampleTaskQueue = "sample-task-queue" - -// application holds the dependencies for our API handlers. -type application struct { - temporalClient client.Client -} - -// runJobRequest defines the expected JSON body for the POST /jobs/run endpoint. -type runJobRequest struct { - Name string `json:"name"` -} - -// runJobResponse defines the JSON response for a successful job submission. -type runJobResponse struct { - WorkflowID string `json:"workflow_id"` - RunID string `json:"run_id"` -} - -// jobStatusResponse defines the JSON response for the job status endpoint. -type jobStatusResponse struct { - WorkflowID string `json:"workflow_id"` - RunID string `json:"run_id"` - Status string `json:"status"` -} - -func main() { - slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil))) - - temporalAddress := os.Getenv("TEMPORAL_ADDRESS") - if temporalAddress == "" { - slog.Warn("TEMPORAL_ADDRESS not set, defaulting to localhost:7233") - temporalAddress = "localhost:7233" - } - - c, err := client.Dial(client.Options{ - HostPort: temporalAddress, - Logger: slog.Default(), - }) - if err != nil { - slog.Error("Unable to create Temporal client", "error", err) - os.Exit(1) - } - defer c.Close() - - app := &application{ - temporalClient: c, - } - - r := chi.NewRouter() - r.Use(middleware.RequestID) - r.Use(middleware.RealIP) - r.Use(middleware.Logger) // Chi's default logger - r.Use(middleware.Recoverer) - - r.Post("/jobs/run", app.runJobHandler) - r.Get("/jobs/{workflowID}/status", app.getJobStatusHandler) - - slog.Info("Starting API server", "port", "8080") - if err := http.ListenAndServe(":8080", r); err != nil { - slog.Error("Failed to start server", "error", err) - } -} - -// runJobHandler starts a new SampleWorkflow execution. -func (app *application) runJobHandler(w http.ResponseWriter, r *http.Request) { - var req runJobRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid request body", http.StatusBadRequest) - return - } - if req.Name == "" { - http.Error(w, "Name field is required", http.StatusBadRequest) - return - } - - options := client.StartWorkflowOptions{ - ID: "sample-workflow-" + uuid.NewString(), - TaskQueue: SampleTaskQueue, - } - - we, err := app.temporalClient.ExecuteWorkflow(context.Background(), options, workflows.SampleWorkflow, req.Name) - if err != nil { - slog.Error("Unable to start workflow", "error", err) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - slog.Info("Started workflow", "workflow_id", we.GetID(), "run_id", we.GetRunID()) - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(runJobResponse{ - WorkflowID: we.GetID(), - RunID: we.GetRunID(), - }) -} - -// getJobStatusHandler retrieves the status of a specific workflow execution. -func (app *application) getJobStatusHandler(w http.ResponseWriter, r *http.Request) { - workflowID := chi.URLParam(r, "workflowID") - // Note: RunID can be empty to get the latest run. - - resp, err := app.temporalClient.DescribeWorkflowExecution(context.Background(), workflowID, "") - if err != nil { - slog.Error("Unable to describe workflow", "error", err, "workflow_id", workflowID) - http.Error(w, "Workflow not found", http.StatusNotFound) - return - } - - status := resp.GetWorkflowExecutionInfo().GetStatus().String() - slog.Info("Described workflow", "workflow_id", workflowID, "status", status) - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(jobStatusResponse{ - WorkflowID: resp.GetWorkflowExecutionInfo().GetExecution().GetWorkflowId(), - RunID: resp.GetWorkflowExecutionInfo().GetExecution().GetRunId(), - Status: status, - }) -} diff --git a/automation-jobs-core/cmd/worker/main.go b/automation-jobs-core/cmd/worker/main.go deleted file mode 100644 index 7d45478..0000000 --- a/automation-jobs-core/cmd/worker/main.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "log/slog" - "os" - - "github.com/lab/automation-jobs-core/temporal/activities" - "github.com/lab/automation-jobs-core/temporal/workflows" - "go.temporal.io/sdk/client" - "go.temporal.io/sdk/worker" -) - -const ( - SampleTaskQueue = "sample-task-queue" -) - -func main() { - // Use slog for structured logging. - slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil))) - - // Get Temporal server address from environment variable. - temporalAddress := os.Getenv("TEMPORAL_ADDRESS") - if temporalAddress == "" { - slog.Warn("TEMPORAL_ADDRESS not set, defaulting to localhost:7233") - temporalAddress = "localhost:7233" - } - - // Create a new Temporal client. - c, err := client.Dial(client.Options{ - HostPort: temporalAddress, - Logger: slog.Default(), - }) - if err != nil { - slog.Error("Unable to create Temporal client", "error", err) - os.Exit(1) - } - defer c.Close() - - // Create a new worker. - w := worker.New(c, SampleTaskQueue, worker.Options{}) - - // Register the workflow and activity. - w.RegisterWorkflow(workflows.SampleWorkflow) - w.RegisterActivity(activities.SampleActivity) - - slog.Info("Starting Temporal worker", "task_queue", SampleTaskQueue) - - // Start the worker. - err = w.Run(worker.InterruptCh()) - if err != nil { - slog.Error("Unable to start worker", "error", err) - os.Exit(1) - } -} diff --git a/automation-jobs-core/go.mod b/automation-jobs-core/go.mod deleted file mode 100644 index 94c5077..0000000 --- a/automation-jobs-core/go.mod +++ /dev/null @@ -1,36 +0,0 @@ -module github.com/lab/automation-jobs-core - -go 1.23.0 - -toolchain go1.23.12 - -require ( - github.com/go-chi/chi/v5 v5.2.3 - github.com/google/uuid v1.6.0 - go.temporal.io/sdk v1.38.0 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect - github.com/nexus-rpc/sdk-go v0.5.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/robfig/cron v1.2.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect - github.com/stretchr/testify v1.10.0 // indirect - go.temporal.io/api v1.54.0 // indirect - golang.org/x/net v0.39.0 // indirect - golang.org/x/sync v0.13.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.24.0 // indirect - golang.org/x/time v0.3.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect - google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.36.6 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/automation-jobs-core/go.sum b/automation-jobs-core/go.sum deleted file mode 100644 index ff67ed4..0000000 --- a/automation-jobs-core/go.sum +++ /dev/null @@ -1,99 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= -github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/nexus-rpc/sdk-go v0.5.1 h1:UFYYfoHlQc+Pn9gQpmn9QE7xluewAn2AO1OSkAh7YFU= -github.com/nexus-rpc/sdk-go v0.5.1/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.temporal.io/api v1.54.0 h1:/sy8rYZEykgmXRjeiv1PkFHLXIus5n6FqGhRtCl7Pc0= -go.temporal.io/api v1.54.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= -go.temporal.io/sdk v1.38.0 h1:4Bok5LEdED7YKpsSjIa3dDqram5VOq+ydBf4pyx0Wo4= -go.temporal.io/sdk v1.38.0/go.mod h1:a+R2Ej28ObvHoILbHaxMyind7M6D+W0L7edt5UJF4SE= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0= -google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/automation-jobs-core/temporal/activities/greeting_activity.go b/automation-jobs-core/temporal/activities/greeting_activity.go deleted file mode 100644 index 72ac458..0000000 --- a/automation-jobs-core/temporal/activities/greeting_activity.go +++ /dev/null @@ -1,10 +0,0 @@ -package activities - -import ( - "context" - "fmt" -) - -func Greet(ctx context.Context, name string) (string, error) { - return fmt.Sprintf("Hello, %s!", name), nil -} diff --git a/automation-jobs-core/temporal/activities/sample_activity.go b/automation-jobs-core/temporal/activities/sample_activity.go deleted file mode 100644 index 529da92..0000000 --- a/automation-jobs-core/temporal/activities/sample_activity.go +++ /dev/null @@ -1,14 +0,0 @@ -package activities - -import ( - "context" - "fmt" - "log/slog" -) - -// SampleActivity is a simple Temporal activity that demonstrates how to receive -// parameters and return a value. -func SampleActivity(ctx context.Context, name string) (string, error) { - slog.Info("Running SampleActivity", "name", name) - return fmt.Sprintf("Hello, %s!", name), nil -} diff --git a/automation-jobs-core/temporal/workflows/greeting_workflow.go b/automation-jobs-core/temporal/workflows/greeting_workflow.go deleted file mode 100644 index 1be98b2..0000000 --- a/automation-jobs-core/temporal/workflows/greeting_workflow.go +++ /dev/null @@ -1,23 +0,0 @@ -package workflows - -import ( - "time" - - "github.com/lab/automation-jobs-core/temporal/activities" - "go.temporal.io/sdk/workflow" -) - -func GreetingWorkflow(ctx workflow.Context, name string) (string, error) { - options := workflow.ActivityOptions{ - StartToCloseTimeout: time.Second * 5, - } - ctx = workflow.WithActivityOptions(ctx, options) - - var result string - err := workflow.ExecuteActivity(ctx, activities.Greet, name).Get(ctx, &result) - if err != nil { - return "", err - } - - return result, nil -} diff --git a/automation-jobs-core/temporal/workflows/sample_workflow.go b/automation-jobs-core/temporal/workflows/sample_workflow.go deleted file mode 100644 index bb9ff6a..0000000 --- a/automation-jobs-core/temporal/workflows/sample_workflow.go +++ /dev/null @@ -1,29 +0,0 @@ -package workflows - -import ( - "time" - - "go.temporal.io/sdk/workflow" - "github.com/lab/automation-jobs-core/temporal/activities" -) - -// SampleWorkflow is a simple Temporal workflow that executes one activity. -func SampleWorkflow(ctx workflow.Context, name string) (string, error) { - // Set a timeout for the activity. - ao := workflow.ActivityOptions{ - StartToCloseTimeout: 10 * time.Second, - } - ctx = workflow.WithActivityOptions(ctx, ao) - - // Execute the activity and wait for its result. - var result string - err := workflow.ExecuteActivity(ctx, activities.SampleActivity, name).Get(ctx, &result) - if err != nil { - workflow.GetLogger(ctx).Error("Activity failed.", "Error", err) - return "", err - } - - workflow.GetLogger(ctx).Info("Workflow completed.", "Result", result) - - return result, nil -} diff --git a/baas-control-plane/.dockerignore b/baas-control-plane/.dockerignore deleted file mode 100644 index c5efd49..0000000 --- a/baas-control-plane/.dockerignore +++ /dev/null @@ -1,10 +0,0 @@ -node_modules -dist -.git -.env -.gitignore -Dockerfile -README.md -BAAS-CONTROL-PLANE.md -migrations -*.log diff --git a/baas-control-plane/.env.example b/baas-control-plane/.env.example deleted file mode 100644 index 5ea2417..0000000 --- a/baas-control-plane/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -PORT=4000 -APPWRITE_ENDPOINT=https://cloud.appwrite.io -APPWRITE_API_KEY=replace-with-appwrite-key -SUPABASE_ENDPOINT=https://api.supabase.com -SUPABASE_SERVICE_KEY=replace-with-supabase-key diff --git a/baas-control-plane/.gitignore b/baas-control-plane/.gitignore deleted file mode 100644 index 2033061..0000000 --- a/baas-control-plane/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules -dist -.env -*.log -.DS_Store -coverage diff --git a/baas-control-plane/BAAS-CONTROL-PLANE.md b/baas-control-plane/BAAS-CONTROL-PLANE.md deleted file mode 100644 index 7db33d1..0000000 --- a/baas-control-plane/BAAS-CONTROL-PLANE.md +++ /dev/null @@ -1,94 +0,0 @@ -# BAAS-CONTROL-PLANE - -O `baas-control-plane` é o orquestrador central para provisionamento e gestão de múltiplos backends-as-a-service (BaaS), como Appwrite e Supabase, oferecendo uma camada unificada de abstração para multi-tenancy. - -## 📋 Visão Geral - -Este serviço não armazena dados de negócio, mas sim metadados sobre tenants, projetos e recursos. Ele atua como um "plano de controle" que delega a criação de infraestrutura para drivers ou provedores específicos. - -### Arquitetura - -```mermaid -graph TD - Client[Dashboard / CLI] -->|HTTP REST| API[Control Plane API] - - subgraph Core Services - API --> Provisioning[Provisioning Service] - API --> Schema[Schema Sync] - API --> Secrets[Secrets Manager] - API --> Audit[Audit Logger] - end - - subgraph Providers - Provisioning -->|Driver Interface| Appwrite[Appwrite Provider] - Provisioning -->|Driver Interface| Supabase[Supabase Provider] - end - - Appwrite -->|API| AWS_Appwrite[Appwrite Instance] - Supabase -->|API| AWS_Supabase[Supabase Hosting] - - API --> DB[(Metadata DB)] -``` - -## 🚀 Estrutura do Projeto - -O projeto segue uma arquitetura modular baseada em **Fastify**: - -| Diretório | Responsabilidade | -| :--- | :--- | -| `src/core` | Configurações globais, plugins do Fastify e tratamento de erros. | -| `src/modules` | Domínios funcionais (Tenants, Projects, etc.). | -| `src/providers` | Implementações dos drivers para cada BaaS suportado. | -| `src/lib` | Utilitários compartilhados. | -| `docs/` | Documentação arquitetural detalhada. | - -## 🛠️ Tecnologias e Otimizações - -- **Backend**: Node.js 20 + Fastify (Alta performance) -- **Linguagem**: TypeScript -- **Validação**: Zod -- **Containerização**: - - Baseada em `gcr.io/distroless/nodejs20-debian12`. - - Multi-stage build para separação de dependências. - - Segurança reforçada (sem shell, usuário non-root). - -## 💻 Como Executar - -### Docker (Recomendado) - -```bash -docker-compose up --build -``` -A API estará disponível na porta `4000`. - -### Desenvolvimento Local - -1. **Instale as dependências:** - ```bash - npm install - ``` - -2. **Configure o ambiente:** - ```bash - cp .env.example .env - ``` - -3. **Execute em modo watch:** - ```bash - npm run dev - ``` - -## 🔌 Fluxos Principais - -1. **Criar Tenant**: Registra uma nova organização no sistema. -2. **Criar Projeto**: Vincula um Tenant a um Provider (ex: Projeto "Marketing" no Appwrite). -3. **Provisionar**: O Control Plane chama a API do Provider para criar bancos de dados, buckets e funções. -4. **Schema Sync**: Aplica definições de coleção/tabela do sistema de forma agnóstica ao provider. - -## 🔧 Detalhes do Dockerfile - -O `Dockerfile` é otimizado para produção e segurança: - -- **Builder**: Compila o TypeScript. -- **Prod Deps**: Instala apenas pacotes necessários para execução (`--omit=dev`). -- **Runtime (Distroless)**: Imagem final minúscula contendo apenas o runtime Node.js e os arquivos da aplicação. diff --git a/baas-control-plane/Dockerfile b/baas-control-plane/Dockerfile deleted file mode 100644 index f02d27f..0000000 --- a/baas-control-plane/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -# Dockerfile -# Stage 1: Build the application -FROM docker.io/library/node:20-alpine AS builder - -WORKDIR /app - -COPY package.json package-lock.json* ./ -RUN npm install - -COPY tsconfig.json ./ -COPY src ./src - -RUN npm run build - -# Stage 2: Install production dependencies -FROM docker.io/library/node:20-alpine AS prod-deps - -WORKDIR /app - -COPY package.json package-lock.json* ./ - -RUN npm install --omit=dev - -# Stage 3: Run the application -FROM gcr.io/distroless/nodejs20-debian12 - -WORKDIR /app - -ENV NODE_ENV=production - -COPY --from=prod-deps /app/node_modules ./node_modules -COPY --from=builder /app/dist ./dist - -EXPOSE 4000 - -CMD ["dist/main.js"] diff --git a/baas-control-plane/data/.gitkeep b/baas-control-plane/data/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/baas-control-plane/docs/architecture.md b/baas-control-plane/docs/architecture.md deleted file mode 100644 index 29ddcfd..0000000 --- a/baas-control-plane/docs/architecture.md +++ /dev/null @@ -1,17 +0,0 @@ -# Arquitetura - -O `baas-control-plane` implementa um control plane modular para gerenciar múltiplos provedores BaaS de forma multi-tenant. Ele centraliza provisioning, schema, secrets, métricas e auditoria sem executar workloads de clientes. - -## Camadas -- **core**: tipos e interface dos providers. -- **providers**: implementações técnicas de Appwrite e Supabase. -- **modules**: serviços de negócio (tenants, projects, provisioning, schema, secrets, finops, audit). -- **lib**: utilitários de ambiente, logger e HTTP. - -## Fluxo básico -1. Tenant é criado e armazenado. -2. Projeto é criado e vinculado a um provider. -3. Provisioning aciona o provider e salva o `externalId`. -4. Schema é versionado e aplicado via provider. -5. FinOps coleta métricas normalizadas. -6. Auditoria registra eventos relevantes. diff --git a/baas-control-plane/docs/providers.md b/baas-control-plane/docs/providers.md deleted file mode 100644 index 473ad3a..0000000 --- a/baas-control-plane/docs/providers.md +++ /dev/null @@ -1,21 +0,0 @@ -# Providers - -Os providers implementam apenas comandos técnicos e não contêm regras de negócio. - -## Interface obrigatória -- `createProject` -- `deleteProject` -- `applySchema` -- `collectMetrics` -- `rotateSecrets` -- `healthCheck` - -## Implementações iniciais -- Appwrite: `src/providers/appwrite` -- Supabase: `src/providers/supabase` - -## Extensão -1. Crie `src/providers/` -2. Implemente `ProviderInterface` -3. Registre no `provider.factory.ts` -4. Configure secrets no `SecretsService` diff --git a/baas-control-plane/docs/security.md b/baas-control-plane/docs/security.md deleted file mode 100644 index 5611fcf..0000000 --- a/baas-control-plane/docs/security.md +++ /dev/null @@ -1,11 +0,0 @@ -# Segurança - -## Princípios -- Providers não acessam `.env` diretamente. -- Secrets são entregues via `SecretsService`. -- Preparado para integração com Vault/Infisical. - -## Boas práticas -- Não faça hardcode de credenciais. -- Rotacione secrets via `rotateSecrets`. -- Audite eventos críticos (tenant, projeto, schema, secrets). diff --git a/baas-control-plane/package-lock.json b/baas-control-plane/package-lock.json deleted file mode 100644 index e97e7aa..0000000 --- a/baas-control-plane/package-lock.json +++ /dev/null @@ -1,1146 +0,0 @@ -{ - "name": "baas-control-plane", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "baas-control-plane", - "version": "1.0.0", - "dependencies": { - "@fastify/cors": "^9.0.1", - "dotenv": "^16.4.5", - "fastify": "^4.27.0", - "zod": "^3.23.8" - }, - "devDependencies": { - "@types/node": "^20.12.12", - "tsx": "^4.15.7", - "typescript": "^5.4.5" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@fastify/ajv-compiler": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", - "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1", - "fast-uri": "^2.0.0" - } - }, - "node_modules/@fastify/cors": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-9.0.1.tgz", - "integrity": "sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==", - "license": "MIT", - "dependencies": { - "fastify-plugin": "^4.0.0", - "mnemonist": "0.39.6" - } - }, - "node_modules/@fastify/error": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", - "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==", - "license": "MIT" - }, - "node_modules/@fastify/fast-json-stringify-compiler": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", - "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", - "license": "MIT", - "dependencies": { - "fast-json-stringify": "^5.7.0" - } - }, - "node_modules/@fastify/merge-json-schemas": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", - "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - } - }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", - "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/abstract-logging": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", - "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", - "license": "MIT" - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv/node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/avvio": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.4.0.tgz", - "integrity": "sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==", - "license": "MIT", - "dependencies": { - "@fastify/error": "^3.3.0", - "fastq": "^1.17.1" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, - "node_modules/fast-content-type-parse": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", - "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==", - "license": "MIT" - }, - "node_modules/fast-decode-uri-component": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", - "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-json-stringify": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", - "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", - "license": "MIT", - "dependencies": { - "@fastify/merge-json-schemas": "^0.1.0", - "ajv": "^8.10.0", - "ajv-formats": "^3.0.1", - "fast-deep-equal": "^3.1.3", - "fast-uri": "^2.1.0", - "json-schema-ref-resolver": "^1.0.1", - "rfdc": "^1.2.0" - } - }, - "node_modules/fast-json-stringify/node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/fast-querystring": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", - "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", - "license": "MIT", - "dependencies": { - "fast-decode-uri-component": "^1.0.1" - } - }, - "node_modules/fast-uri": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", - "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", - "license": "MIT" - }, - "node_modules/fastify": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.29.1.tgz", - "integrity": "sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/ajv-compiler": "^3.5.0", - "@fastify/error": "^3.4.0", - "@fastify/fast-json-stringify-compiler": "^4.3.0", - "abstract-logging": "^2.0.1", - "avvio": "^8.3.0", - "fast-content-type-parse": "^1.1.0", - "fast-json-stringify": "^5.8.0", - "find-my-way": "^8.0.0", - "light-my-request": "^5.11.0", - "pino": "^9.0.0", - "process-warning": "^3.0.0", - "proxy-addr": "^2.0.7", - "rfdc": "^1.3.0", - "secure-json-parse": "^2.7.0", - "semver": "^7.5.4", - "toad-cache": "^3.3.0" - } - }, - "node_modules/fastify-plugin": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.5.1.tgz", - "integrity": "sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==", - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/find-my-way": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.2.tgz", - "integrity": "sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-querystring": "^1.0.0", - "safe-regex2": "^3.1.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/json-schema-ref-resolver": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", - "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/light-my-request": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz", - "integrity": "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==", - "license": "BSD-3-Clause", - "dependencies": { - "cookie": "^0.7.0", - "process-warning": "^3.0.0", - "set-cookie-parser": "^2.4.1" - } - }, - "node_modules/mnemonist": { - "version": "0.39.6", - "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.6.tgz", - "integrity": "sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==", - "license": "MIT", - "dependencies": { - "obliterator": "^2.0.1" - } - }, - "node_modules/obliterator": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz", - "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==", - "license": "MIT" - }, - "node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/pino": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", - "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", - "license": "MIT", - "dependencies": { - "@pinojs/redact": "^0.4.0", - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-std-serializers": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", - "license": "MIT" - }, - "node_modules/pino/node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/process-warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", - "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "license": "MIT" - }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/ret": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", - "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/safe-regex2": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", - "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", - "license": "MIT", - "dependencies": { - "ret": "~0.4.0" - } - }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", - "license": "BSD-3-Clause" - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", - "license": "MIT" - }, - "node_modules/sonic-boom": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - } - }, - "node_modules/toad-cache": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", - "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/baas-control-plane/package.json b/baas-control-plane/package.json deleted file mode 100644 index ae7526a..0000000 --- a/baas-control-plane/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "baas-control-plane", - "private": true, - "version": "1.0.0", - "type": "module", - "scripts": { - "dev": "tsx watch src/main.ts", - "build": "tsc", - "start": "node dist/main.js" - }, - "dependencies": { - "@fastify/cors": "^9.0.1", - "dotenv": "^16.4.5", - "fastify": "^4.27.0", - "zod": "^3.23.8" - }, - "devDependencies": { - "@types/node": "^20.12.12", - "tsx": "^4.15.7", - "typescript": "^5.4.5" - } -} diff --git a/baas-control-plane/src/core/provider.factory.ts b/baas-control-plane/src/core/provider.factory.ts deleted file mode 100644 index 2a027ad..0000000 --- a/baas-control-plane/src/core/provider.factory.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ProviderInterface } from './provider.interface.js'; -import { ProviderType } from './types.js'; -import { AppwriteProvider } from '../providers/appwrite/appwrite.provisioning.js'; -import { SupabaseProvider } from '../providers/supabase/supabase.provisioning.js'; - -const providerRegistry: Record ProviderInterface> = { - appwrite: () => new AppwriteProvider(), - supabase: () => new SupabaseProvider(), -}; - -export const providerFactory = { - create(type: ProviderType): ProviderInterface { - const providerBuilder = providerRegistry[type]; - if (!providerBuilder) { - throw new Error(`Provider ${type} is not registered`); - } - return providerBuilder(); - }, -}; diff --git a/baas-control-plane/src/core/provider.interface.ts b/baas-control-plane/src/core/provider.interface.ts deleted file mode 100644 index 2de4510..0000000 --- a/baas-control-plane/src/core/provider.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ProviderMetrics, ProviderProject, ProviderSecrets, SchemaDefinition } from './types.js'; - -export interface ProviderInterface { - createProject(name: string, secrets: ProviderSecrets): Promise; - deleteProject(externalId: string, secrets: ProviderSecrets): Promise; - applySchema(externalId: string, schema: SchemaDefinition, secrets: ProviderSecrets): Promise; - collectMetrics(externalId: string, secrets: ProviderSecrets): Promise; - rotateSecrets(externalId: string, secrets: ProviderSecrets): Promise; - healthCheck(secrets: ProviderSecrets): Promise; -} diff --git a/baas-control-plane/src/core/types.ts b/baas-control-plane/src/core/types.ts deleted file mode 100644 index d219ae9..0000000 --- a/baas-control-plane/src/core/types.ts +++ /dev/null @@ -1,59 +0,0 @@ -export type ProviderType = 'appwrite' | 'supabase'; - -export type TenantStatus = 'active' | 'suspended'; - -export type ProjectStatus = 'draft' | 'provisioning' | 'provisioned' | 'failed'; - -export interface Tenant { - id: string; - name: string; - plan: string; - status: TenantStatus; - createdAt: string; - updatedAt: string; -} - -export interface Project { - id: string; - tenantId: string; - name: string; - provider: ProviderType; - status: ProjectStatus; - externalId?: string; - createdAt: string; - updatedAt: string; -} - -export interface SchemaDefinition { - version: string; - payload: Record; -} - -export interface ProviderProject { - externalId: string; - dashboardUrl?: string; - metadata?: Record; -} - -export interface ProviderMetrics { - users: number; - storageMb: number; - requests: number; - functions: number; - capturedAt: string; -} - -export interface ProviderSecrets { - endpoint: string; - apiKey: string; - projectRef?: string; -} - -export interface AuditEvent { - id: string; - tenantId?: string; - projectId?: string; - action: string; - metadata?: Record; - createdAt: string; -} diff --git a/baas-control-plane/src/lib/env.ts b/baas-control-plane/src/lib/env.ts deleted file mode 100644 index ae166fe..0000000 --- a/baas-control-plane/src/lib/env.ts +++ /dev/null @@ -1,22 +0,0 @@ -import dotenv from 'dotenv'; -import { z } from 'zod'; - -dotenv.config(); - -const envSchema = z.object({ - PORT: z.string().default('4000'), - APPWRITE_ENDPOINT: z.string().default('https://cloud.appwrite.io'), - APPWRITE_API_KEY: z.string().default('appwrite-api-key'), - SUPABASE_ENDPOINT: z.string().default('https://api.supabase.com'), - SUPABASE_SERVICE_KEY: z.string().default('supabase-service-key'), -}); - -const parsed = envSchema.parse(process.env); - -export const env = { - port: Number(parsed.PORT), - appwriteEndpoint: parsed.APPWRITE_ENDPOINT, - appwriteApiKey: parsed.APPWRITE_API_KEY, - supabaseEndpoint: parsed.SUPABASE_ENDPOINT, - supabaseServiceKey: parsed.SUPABASE_SERVICE_KEY, -}; diff --git a/baas-control-plane/src/lib/http.ts b/baas-control-plane/src/lib/http.ts deleted file mode 100644 index c5764f4..0000000 --- a/baas-control-plane/src/lib/http.ts +++ /dev/null @@ -1,24 +0,0 @@ -export const http = { - async get(url: string, options?: RequestInit): Promise { - const response = await fetch(url, { ...options, method: 'GET' }); - if (!response.ok) { - throw new Error(`HTTP GET failed with status ${response.status}`); - } - return response.json() as Promise; - }, - async post(url: string, body?: unknown, options?: RequestInit): Promise { - const response = await fetch(url, { - ...options, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...(options?.headers ?? {}), - }, - body: body ? JSON.stringify(body) : undefined, - }); - if (!response.ok) { - throw new Error(`HTTP POST failed with status ${response.status}`); - } - return response.json() as Promise; - }, -}; diff --git a/baas-control-plane/src/lib/logger.ts b/baas-control-plane/src/lib/logger.ts deleted file mode 100644 index 7aff104..0000000 --- a/baas-control-plane/src/lib/logger.ts +++ /dev/null @@ -1,34 +0,0 @@ -type LogPayload = Record; - -const log = (level: 'info' | 'error' | 'warn', message: string, payload?: LogPayload) => { - const entry = { - level, - message, - timestamp: new Date().toISOString(), - ...payload, - }; - - if (level === 'error') { - console.error(entry); - return; - } - - if (level === 'warn') { - console.warn(entry); - return; - } - - console.log(entry); -}; - -export const logger = { - info(message: string, payload?: LogPayload) { - log('info', message, payload); - }, - warn(message: string, payload?: LogPayload) { - log('warn', message, payload); - }, - error(message: string, payload?: LogPayload) { - log('error', message, payload); - }, -}; diff --git a/baas-control-plane/src/lib/storage.ts b/baas-control-plane/src/lib/storage.ts deleted file mode 100644 index 54e233e..0000000 --- a/baas-control-plane/src/lib/storage.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { promises as fs } from 'fs'; -import path from 'path'; - -const dataDir = path.resolve('data'); - -const ensureDir = async () => { - await fs.mkdir(dataDir, { recursive: true }); -}; - -const filePath = (file: string) => path.join(dataDir, file); - -export const storage = { - async readCollection(file: string): Promise { - await ensureDir(); - try { - const content = await fs.readFile(filePath(file), 'utf-8'); - return JSON.parse(content) as T[]; - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') { - return []; - } - throw error; - } - }, - async writeCollection(file: string, data: T[]): Promise { - await ensureDir(); - await fs.writeFile(filePath(file), JSON.stringify(data, null, 2)); - }, -}; diff --git a/baas-control-plane/src/main.ts b/baas-control-plane/src/main.ts deleted file mode 100644 index 728dc2d..0000000 --- a/baas-control-plane/src/main.ts +++ /dev/null @@ -1,57 +0,0 @@ -import Fastify from 'fastify'; -import cors from '@fastify/cors'; -import { env } from './lib/env.js'; -import { logger } from './lib/logger.js'; -import { TenantsService } from './modules/tenants/tenants.service.js'; -import { ProjectsService } from './modules/projects/projects.service.js'; -import { ProvisioningService } from './modules/provisioning/provisioning.service.js'; -import { SchemaService } from './modules/schema/schema.service.js'; -import { SchemaVersioning } from './modules/schema/schema.versioning.js'; -import { SecretsService } from './modules/secrets/secrets.service.js'; -import { AuditService } from './modules/audit/audit.service.js'; -import { FinopsCollector } from './modules/finops/finops.collector.js'; -import { registerTenantsController } from './modules/tenants/tenants.controller.js'; -import { registerProjectsController } from './modules/projects/projects.controller.js'; -import { providerFactory } from './core/provider.factory.js'; - -const app = Fastify({ logger: false }); - -await app.register(cors, { origin: true }); - -const tenantsService = new TenantsService(); -const projectsService = new ProjectsService(); -const secretsService = new SecretsService(); -const auditService = new AuditService(); -const provisioningService = new ProvisioningService(projectsService, secretsService); -const schemaService = new SchemaService(projectsService, secretsService, new SchemaVersioning()); -const finopsCollector = new FinopsCollector(projectsService, secretsService); - -app.get('/health', async () => { - const appwrite = providerFactory.create('appwrite'); - const supabase = providerFactory.create('supabase'); - - const [appwriteHealthy, supabaseHealthy] = await Promise.all([ - appwrite.healthCheck(await secretsService.getProviderSecrets('appwrite', 'system')), - supabase.healthCheck(await secretsService.getProviderSecrets('supabase', 'system')), - ]); - - return { - status: 'ok', - providers: { - appwrite: appwriteHealthy, - supabase: supabaseHealthy, - }, - }; -}); - -registerTenantsController(app, tenantsService, auditService); -registerProjectsController(app, projectsService, provisioningService, schemaService, auditService, finopsCollector); - -app.setErrorHandler((error, _request, reply) => { - logger.error('Request failed', { message: error.message }); - reply.status(400).send({ error: error.message }); -}); - -app.listen({ port: env.port, host: '0.0.0.0' }).then(() => { - logger.info(`baas-control-plane listening on http://localhost:${env.port}`); -}); diff --git a/baas-control-plane/src/modules/audit/audit.service.ts b/baas-control-plane/src/modules/audit/audit.service.ts deleted file mode 100644 index 5484893..0000000 --- a/baas-control-plane/src/modules/audit/audit.service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { storage } from '../../lib/storage.js'; -import { AuditEvent } from '../../core/types.js'; - -const AUDIT_FILE = 'audit-events.json'; - -export class AuditService { - async record(event: Omit): Promise { - const events = await storage.readCollection(AUDIT_FILE); - const entry: AuditEvent = { - id: crypto.randomUUID(), - createdAt: new Date().toISOString(), - ...event, - }; - events.push(entry); - await storage.writeCollection(AUDIT_FILE, events); - return entry; - } -} diff --git a/baas-control-plane/src/modules/finops/finops.collector.ts b/baas-control-plane/src/modules/finops/finops.collector.ts deleted file mode 100644 index 7a93f1c..0000000 --- a/baas-control-plane/src/modules/finops/finops.collector.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { providerFactory } from '../../core/provider.factory.js'; -import { ProjectsService } from '../projects/projects.service.js'; -import { SecretsService } from '../secrets/secrets.service.js'; -import { ProviderMetrics } from '../../core/types.js'; - -export class FinopsCollector { - constructor( - private readonly projectsService: ProjectsService, - private readonly secretsService: SecretsService, - ) {} - - async collectForProject(projectId: string): Promise { - const project = await this.projectsService.getProject(projectId); - if (!project || !project.externalId) { - throw new Error('Project not provisioned'); - } - - const provider = providerFactory.create(project.provider); - const secrets = await this.secretsService.getProviderSecrets(project.provider, project.tenantId); - return provider.collectMetrics(project.externalId, secrets); - } -} diff --git a/baas-control-plane/src/modules/projects/projects.controller.ts b/baas-control-plane/src/modules/projects/projects.controller.ts deleted file mode 100644 index c027908..0000000 --- a/baas-control-plane/src/modules/projects/projects.controller.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { FastifyInstance } from 'fastify'; -import { z } from 'zod'; -import { ProjectsService } from './projects.service.js'; -import { ProvisioningService } from '../provisioning/provisioning.service.js'; -import { SchemaService } from '../schema/schema.service.js'; -import { AuditService } from '../audit/audit.service.js'; -import { FinopsCollector } from '../finops/finops.collector.js'; - -const projectSchema = z.object({ - name: z.string().min(2), - provider: z.enum(['appwrite', 'supabase']), -}); - -const schemaSyncPayload = z.object({ - version: z.string().min(1), - payload: z.record(z.unknown()), -}); - -export const registerProjectsController = ( - app: FastifyInstance, - projectsService: ProjectsService, - provisioningService: ProvisioningService, - schemaService: SchemaService, - auditService: AuditService, - finopsCollector: FinopsCollector, -) => { - app.get('/tenants/:tenantId/projects', async (request) => { - const { tenantId } = request.params as { tenantId: string }; - return projectsService.listProjectsForTenant(tenantId); - }); - - app.post('/tenants/:tenantId/projects', async (request, reply) => { - const { tenantId } = request.params as { tenantId: string }; - const payload = projectSchema.parse(request.body); - const project = await projectsService.createProject(tenantId, payload); - await auditService.record({ - tenantId, - projectId: project.id, - action: 'project.created', - metadata: { provider: project.provider }, - }); - reply.code(201); - return project; - }); - - app.post('/projects/:projectId/provision', async (request) => { - const { projectId } = request.params as { projectId: string }; - const result = await provisioningService.provisionProject(projectId); - await auditService.record({ - projectId, - tenantId: result.project.tenantId, - action: 'project.provisioned', - metadata: { provider: result.project.provider, externalId: result.project.externalId }, - }); - return result; - }); - - app.post('/projects/:projectId/schema/sync', async (request) => { - const { projectId } = request.params as { projectId: string }; - const payload = schemaSyncPayload.parse(request.body); - const result = await schemaService.syncSchema(projectId, payload); - await auditService.record({ - projectId, - tenantId: result.project.tenantId, - action: 'schema.applied', - metadata: { version: payload.version }, - }); - return result; - }); - - app.get('/projects/:projectId/metrics', async (request) => { - const { projectId } = request.params as { projectId: string }; - return finopsCollector.collectForProject(projectId); - }); -}; diff --git a/baas-control-plane/src/modules/projects/projects.entity.ts b/baas-control-plane/src/modules/projects/projects.entity.ts deleted file mode 100644 index 3f6d399..0000000 --- a/baas-control-plane/src/modules/projects/projects.entity.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Project } from '../../core/types.js'; - -export type ProjectEntity = Project; diff --git a/baas-control-plane/src/modules/projects/projects.service.ts b/baas-control-plane/src/modules/projects/projects.service.ts deleted file mode 100644 index 0e8260d..0000000 --- a/baas-control-plane/src/modules/projects/projects.service.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { storage } from '../../lib/storage.js'; -import { Project, ProviderType } from '../../core/types.js'; - -const PROJECTS_FILE = 'projects.json'; - -export class ProjectsService { - async listProjectsForTenant(tenantId: string): Promise { - const projects = await storage.readCollection(PROJECTS_FILE); - return projects.filter((project) => project.tenantId === tenantId); - } - - async getProject(projectId: string): Promise { - const projects = await storage.readCollection(PROJECTS_FILE); - return projects.find((project) => project.id === projectId); - } - - async createProject(tenantId: string, input: { name: string; provider: ProviderType }): Promise { - const projects = await storage.readCollection(PROJECTS_FILE); - const now = new Date().toISOString(); - const project: Project = { - id: crypto.randomUUID(), - tenantId, - name: input.name, - provider: input.provider, - status: 'draft', - createdAt: now, - updatedAt: now, - }; - projects.push(project); - await storage.writeCollection(PROJECTS_FILE, projects); - return project; - } - - async updateProject(projectId: string, changes: Partial): Promise { - const projects = await storage.readCollection(PROJECTS_FILE); - const index = projects.findIndex((project) => project.id === projectId); - if (index === -1) { - throw new Error('Project not found'); - } - const updated = { - ...projects[index], - ...changes, - updatedAt: new Date().toISOString(), - }; - projects[index] = updated; - await storage.writeCollection(PROJECTS_FILE, projects); - return updated; - } -} diff --git a/baas-control-plane/src/modules/provisioning/provisioning.service.ts b/baas-control-plane/src/modules/provisioning/provisioning.service.ts deleted file mode 100644 index 691ac87..0000000 --- a/baas-control-plane/src/modules/provisioning/provisioning.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ProjectsService } from '../projects/projects.service.js'; -import { providerFactory } from '../../core/provider.factory.js'; -import { SecretsService } from '../secrets/secrets.service.js'; -import { Project } from '../../core/types.js'; - -export class ProvisioningService { - constructor( - private readonly projectsService: ProjectsService, - private readonly secretsService: SecretsService, - ) {} - - async provisionProject(projectId: string): Promise<{ project: Project }> { - const project = await this.projectsService.getProject(projectId); - if (!project) { - throw new Error('Project not found'); - } - - const provider = providerFactory.create(project.provider); - const secrets = await this.secretsService.getProviderSecrets(project.provider, project.tenantId); - - const created = await provider.createProject(project.name, secrets); - const updated = await this.projectsService.updateProject(projectId, { - status: 'provisioned', - externalId: created.externalId, - }); - - return { project: updated }; - } -} diff --git a/baas-control-plane/src/modules/schema/schema.service.ts b/baas-control-plane/src/modules/schema/schema.service.ts deleted file mode 100644 index eb25af3..0000000 --- a/baas-control-plane/src/modules/schema/schema.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { SchemaDefinition } from '../../core/types.js'; -import { ProjectsService } from '../projects/projects.service.js'; -import { providerFactory } from '../../core/provider.factory.js'; -import { SecretsService } from '../secrets/secrets.service.js'; -import { SchemaVersioning } from './schema.versioning.js'; - -export class SchemaService { - constructor( - private readonly projectsService: ProjectsService, - private readonly secretsService: SecretsService, - private readonly versioning: SchemaVersioning, - ) {} - - async syncSchema(projectId: string, schema: SchemaDefinition): Promise<{ project: { id: string; tenantId: string } }> { - const project = await this.projectsService.getProject(projectId); - if (!project || !project.externalId) { - throw new Error('Project not provisioned'); - } - - const provider = providerFactory.create(project.provider); - const secrets = await this.secretsService.getProviderSecrets(project.provider, project.tenantId); - await provider.applySchema(project.externalId, schema, secrets); - await this.versioning.addVersion(projectId, schema); - - return { project: { id: project.id, tenantId: project.tenantId } }; - } -} diff --git a/baas-control-plane/src/modules/schema/schema.versioning.ts b/baas-control-plane/src/modules/schema/schema.versioning.ts deleted file mode 100644 index f8de273..0000000 --- a/baas-control-plane/src/modules/schema/schema.versioning.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { storage } from '../../lib/storage.js'; -import { SchemaDefinition } from '../../core/types.js'; - -const SCHEMA_FILE = 'schema-versions.json'; - -interface SchemaVersionRecord { - projectId: string; - versions: SchemaDefinition[]; -} - -export class SchemaVersioning { - async listVersions(projectId: string): Promise { - const records = await storage.readCollection(SCHEMA_FILE); - const record = records.find((item) => item.projectId === projectId); - return record?.versions ?? []; - } - - async addVersion(projectId: string, schema: SchemaDefinition): Promise { - const records = await storage.readCollection(SCHEMA_FILE); - const existing = records.find((item) => item.projectId === projectId); - if (existing) { - existing.versions.push(schema); - } else { - records.push({ projectId, versions: [schema] }); - } - await storage.writeCollection(SCHEMA_FILE, records); - } -} diff --git a/baas-control-plane/src/modules/secrets/secrets.service.ts b/baas-control-plane/src/modules/secrets/secrets.service.ts deleted file mode 100644 index b8a72b6..0000000 --- a/baas-control-plane/src/modules/secrets/secrets.service.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { env } from '../../lib/env.js'; -import { ProviderSecrets, ProviderType } from '../../core/types.js'; -import { storage } from '../../lib/storage.js'; - -const SECRETS_FILE = 'provider-secrets.json'; - -interface SecretsRecord { - tenantId: string; - provider: ProviderType; - secrets: ProviderSecrets; -} - -export class SecretsService { - async getProviderSecrets(provider: ProviderType, tenantId: string): Promise { - const records = await storage.readCollection(SECRETS_FILE); - const record = records.find((item) => item.tenantId === tenantId && item.provider === provider); - - if (record) { - return record.secrets; - } - - const defaults: Record = { - appwrite: { - endpoint: env.appwriteEndpoint, - apiKey: env.appwriteApiKey, - }, - supabase: { - endpoint: env.supabaseEndpoint, - apiKey: env.supabaseServiceKey, - }, - }; - - return defaults[provider]; - } - - async rotateProviderSecrets( - provider: ProviderType, - tenantId: string, - secrets: ProviderSecrets, - ): Promise { - const records = await storage.readCollection(SECRETS_FILE); - const existing = records.find((item) => item.tenantId === tenantId && item.provider === provider); - - if (existing) { - existing.secrets = secrets; - } else { - records.push({ tenantId, provider, secrets }); - } - - await storage.writeCollection(SECRETS_FILE, records); - } -} diff --git a/baas-control-plane/src/modules/tenants/tenants.controller.ts b/baas-control-plane/src/modules/tenants/tenants.controller.ts deleted file mode 100644 index b71ec39..0000000 --- a/baas-control-plane/src/modules/tenants/tenants.controller.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { FastifyInstance } from 'fastify'; -import { z } from 'zod'; -import { TenantsService } from './tenants.service.js'; -import { AuditService } from '../audit/audit.service.js'; - -const tenantSchema = z.object({ - name: z.string().min(2), - plan: z.string().optional(), - status: z.enum(['active', 'suspended']).optional(), -}); - -export const registerTenantsController = ( - app: FastifyInstance, - tenantsService: TenantsService, - auditService: AuditService, -) => { - app.get('/tenants', async () => tenantsService.listTenants()); - - app.post('/tenants', async (request, reply) => { - const payload = tenantSchema.parse(request.body); - const tenant = await tenantsService.createTenant(payload); - await auditService.record({ - tenantId: tenant.id, - action: 'tenant.created', - metadata: { name: tenant.name, plan: tenant.plan }, - }); - reply.code(201); - return tenant; - }); -}; diff --git a/baas-control-plane/src/modules/tenants/tenants.entity.ts b/baas-control-plane/src/modules/tenants/tenants.entity.ts deleted file mode 100644 index 6d1365f..0000000 --- a/baas-control-plane/src/modules/tenants/tenants.entity.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Tenant } from '../../core/types.js'; - -export type TenantEntity = Tenant; diff --git a/baas-control-plane/src/modules/tenants/tenants.service.ts b/baas-control-plane/src/modules/tenants/tenants.service.ts deleted file mode 100644 index 94efa7e..0000000 --- a/baas-control-plane/src/modules/tenants/tenants.service.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { storage } from '../../lib/storage.js'; -import { logger } from '../../lib/logger.js'; -import { Tenant, TenantStatus } from '../../core/types.js'; - -const TENANTS_FILE = 'tenants.json'; - -export class TenantsService { - async listTenants(): Promise { - return storage.readCollection(TENANTS_FILE); - } - - async getTenant(id: string): Promise { - const tenants = await storage.readCollection(TENANTS_FILE); - return tenants.find((tenant) => tenant.id === id); - } - - async createTenant(input: { name: string; plan?: string; status?: TenantStatus }): Promise { - const tenants = await storage.readCollection(TENANTS_FILE); - const now = new Date().toISOString(); - const tenant: Tenant = { - id: crypto.randomUUID(), - name: input.name, - plan: input.plan ?? 'standard', - status: input.status ?? 'active', - createdAt: now, - updatedAt: now, - }; - tenants.push(tenant); - await storage.writeCollection(TENANTS_FILE, tenants); - logger.info('Tenant created', { tenantId: tenant.id }); - return tenant; - } -} diff --git a/baas-control-plane/src/providers/appwrite/appwrite.client.ts b/baas-control-plane/src/providers/appwrite/appwrite.client.ts deleted file mode 100644 index d8204c8..0000000 --- a/baas-control-plane/src/providers/appwrite/appwrite.client.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ProviderMetrics, ProviderProject, ProviderSecrets, SchemaDefinition } from '../../core/types.js'; -import { logger } from '../../lib/logger.js'; - -export class AppwriteClient { - async createProject(name: string, secrets: ProviderSecrets): Promise { - logger.info('Appwrite create project requested', { name, endpoint: secrets.endpoint }); - return { - externalId: `appwrite_${crypto.randomUUID()}`, - dashboardUrl: `${secrets.endpoint}/console/project`, - }; - } - - async deleteProject(externalId: string, secrets: ProviderSecrets): Promise { - logger.info('Appwrite delete project requested', { externalId, endpoint: secrets.endpoint }); - } - - async applySchema(externalId: string, schema: SchemaDefinition, secrets: ProviderSecrets): Promise { - logger.info('Appwrite apply schema requested', { - externalId, - version: schema.version, - endpoint: secrets.endpoint, - }); - } - - async collectMetrics(externalId: string, secrets: ProviderSecrets): Promise { - logger.info('Appwrite metrics requested', { externalId, endpoint: secrets.endpoint }); - return { - users: 0, - storageMb: 0, - requests: 0, - functions: 0, - capturedAt: new Date().toISOString(), - }; - } - - async rotateSecrets(externalId: string, secrets: ProviderSecrets): Promise { - logger.info('Appwrite secrets rotation requested', { externalId, endpoint: secrets.endpoint }); - return { - ...secrets, - apiKey: `${secrets.apiKey}-rotated`, - }; - } - - async healthCheck(secrets: ProviderSecrets): Promise { - logger.info('Appwrite health check requested', { endpoint: secrets.endpoint }); - return true; - } -} diff --git a/baas-control-plane/src/providers/appwrite/appwrite.metrics.ts b/baas-control-plane/src/providers/appwrite/appwrite.metrics.ts deleted file mode 100644 index a293239..0000000 --- a/baas-control-plane/src/providers/appwrite/appwrite.metrics.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ProviderMetrics, ProviderSecrets } from '../../core/types.js'; -import { AppwriteClient } from './appwrite.client.js'; - -const client = new AppwriteClient(); - -export const appwriteMetrics = { - async collect(externalId: string, secrets: ProviderSecrets): Promise { - return client.collectMetrics(externalId, secrets); - }, -}; diff --git a/baas-control-plane/src/providers/appwrite/appwrite.provisioning.ts b/baas-control-plane/src/providers/appwrite/appwrite.provisioning.ts deleted file mode 100644 index fdb99f1..0000000 --- a/baas-control-plane/src/providers/appwrite/appwrite.provisioning.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ProviderInterface } from '../../core/provider.interface.js'; -import { ProviderMetrics, ProviderProject, ProviderSecrets, SchemaDefinition } from '../../core/types.js'; -import { AppwriteClient } from './appwrite.client.js'; - -export class AppwriteProvider implements ProviderInterface { - private readonly client = new AppwriteClient(); - - async createProject(name: string, secrets: ProviderSecrets): Promise { - return this.client.createProject(name, secrets); - } - - async deleteProject(externalId: string, secrets: ProviderSecrets): Promise { - await this.client.deleteProject(externalId, secrets); - } - - async applySchema(externalId: string, schema: SchemaDefinition, secrets: ProviderSecrets): Promise { - await this.client.applySchema(externalId, schema, secrets); - } - - async collectMetrics(externalId: string, secrets: ProviderSecrets): Promise { - return this.client.collectMetrics(externalId, secrets); - } - - async rotateSecrets(externalId: string, secrets: ProviderSecrets): Promise { - return this.client.rotateSecrets(externalId, secrets); - } - - async healthCheck(secrets: ProviderSecrets): Promise { - return this.client.healthCheck(secrets); - } -} diff --git a/baas-control-plane/src/providers/appwrite/appwrite.schema.ts b/baas-control-plane/src/providers/appwrite/appwrite.schema.ts deleted file mode 100644 index 3feee19..0000000 --- a/baas-control-plane/src/providers/appwrite/appwrite.schema.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { SchemaDefinition, ProviderSecrets } from '../../core/types.js'; -import { AppwriteClient } from './appwrite.client.js'; - -const client = new AppwriteClient(); - -export const appwriteSchema = { - async apply(externalId: string, schema: SchemaDefinition, secrets: ProviderSecrets): Promise { - await client.applySchema(externalId, schema, secrets); - }, -}; diff --git a/baas-control-plane/src/providers/supabase/supabase.client.ts b/baas-control-plane/src/providers/supabase/supabase.client.ts deleted file mode 100644 index cdba5a8..0000000 --- a/baas-control-plane/src/providers/supabase/supabase.client.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ProviderMetrics, ProviderProject, ProviderSecrets, SchemaDefinition } from '../../core/types.js'; -import { logger } from '../../lib/logger.js'; - -export class SupabaseClient { - async createProject(name: string, secrets: ProviderSecrets): Promise { - logger.info('Supabase create project requested', { name, endpoint: secrets.endpoint }); - return { - externalId: `supabase_${crypto.randomUUID()}`, - dashboardUrl: `${secrets.endpoint}/project`, - }; - } - - async deleteProject(externalId: string, secrets: ProviderSecrets): Promise { - logger.info('Supabase delete project requested', { externalId, endpoint: secrets.endpoint }); - } - - async applySchema(externalId: string, schema: SchemaDefinition, secrets: ProviderSecrets): Promise { - logger.info('Supabase apply schema requested', { - externalId, - version: schema.version, - endpoint: secrets.endpoint, - }); - } - - async collectMetrics(externalId: string, secrets: ProviderSecrets): Promise { - logger.info('Supabase metrics requested', { externalId, endpoint: secrets.endpoint }); - return { - users: 0, - storageMb: 0, - requests: 0, - functions: 0, - capturedAt: new Date().toISOString(), - }; - } - - async rotateSecrets(externalId: string, secrets: ProviderSecrets): Promise { - logger.info('Supabase secrets rotation requested', { externalId, endpoint: secrets.endpoint }); - return { - ...secrets, - apiKey: `${secrets.apiKey}-rotated`, - }; - } - - async healthCheck(secrets: ProviderSecrets): Promise { - logger.info('Supabase health check requested', { endpoint: secrets.endpoint }); - return true; - } -} diff --git a/baas-control-plane/src/providers/supabase/supabase.metrics.ts b/baas-control-plane/src/providers/supabase/supabase.metrics.ts deleted file mode 100644 index e331300..0000000 --- a/baas-control-plane/src/providers/supabase/supabase.metrics.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ProviderMetrics, ProviderSecrets } from '../../core/types.js'; -import { SupabaseClient } from './supabase.client.js'; - -const client = new SupabaseClient(); - -export const supabaseMetrics = { - async collect(externalId: string, secrets: ProviderSecrets): Promise { - return client.collectMetrics(externalId, secrets); - }, -}; diff --git a/baas-control-plane/src/providers/supabase/supabase.provisioning.ts b/baas-control-plane/src/providers/supabase/supabase.provisioning.ts deleted file mode 100644 index c008a6c..0000000 --- a/baas-control-plane/src/providers/supabase/supabase.provisioning.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ProviderInterface } from '../../core/provider.interface.js'; -import { ProviderMetrics, ProviderProject, ProviderSecrets, SchemaDefinition } from '../../core/types.js'; -import { SupabaseClient } from './supabase.client.js'; - -export class SupabaseProvider implements ProviderInterface { - private readonly client = new SupabaseClient(); - - async createProject(name: string, secrets: ProviderSecrets): Promise { - return this.client.createProject(name, secrets); - } - - async deleteProject(externalId: string, secrets: ProviderSecrets): Promise { - await this.client.deleteProject(externalId, secrets); - } - - async applySchema(externalId: string, schema: SchemaDefinition, secrets: ProviderSecrets): Promise { - await this.client.applySchema(externalId, schema, secrets); - } - - async collectMetrics(externalId: string, secrets: ProviderSecrets): Promise { - return this.client.collectMetrics(externalId, secrets); - } - - async rotateSecrets(externalId: string, secrets: ProviderSecrets): Promise { - return this.client.rotateSecrets(externalId, secrets); - } - - async healthCheck(secrets: ProviderSecrets): Promise { - return this.client.healthCheck(secrets); - } -} diff --git a/baas-control-plane/src/providers/supabase/supabase.schema.ts b/baas-control-plane/src/providers/supabase/supabase.schema.ts deleted file mode 100644 index 5f0c671..0000000 --- a/baas-control-plane/src/providers/supabase/supabase.schema.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ProviderSecrets, SchemaDefinition } from '../../core/types.js'; -import { SupabaseClient } from './supabase.client.js'; - -const client = new SupabaseClient(); - -export const supabaseSchema = { - async apply(externalId: string, schema: SchemaDefinition, secrets: ProviderSecrets): Promise { - await client.applySchema(externalId, schema, secrets); - }, -}; diff --git a/baas-control-plane/tsconfig.json b/baas-control-plane/tsconfig.json deleted file mode 100644 index d9fba83..0000000 --- a/baas-control-plane/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "outDir": "dist", - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "resolveJsonModule": true - }, - "include": ["src/**/*.ts"] -} diff --git a/billing-finance-core/.dockerignore b/billing-finance-core/.dockerignore deleted file mode 100644 index bf066d2..0000000 --- a/billing-finance-core/.dockerignore +++ /dev/null @@ -1,10 +0,0 @@ -node_modules -dist -.git -.env -.gitignore -Dockerfile -README.md -BILLING-FINANCE-CORE.md -prisma/migrations -*.log diff --git a/billing-finance-core/.env.example b/billing-finance-core/.env.example deleted file mode 100644 index ca8a203..0000000 --- a/billing-finance-core/.env.example +++ /dev/null @@ -1,9 +0,0 @@ -NODE_ENV=development -PORT=3000 -DATABASE_URL=postgresql://postgres:postgres@localhost:5432/billing_finance_core -JWT_SECRET=change-me -JWT_PUBLIC_KEY= -JWT_ISSUER=identity-gateway -PAYMENT_WEBHOOK_SECRET=change-me -STRIPE_API_KEY= -APP_LOG_LEVEL=info diff --git a/billing-finance-core/.eslintrc.js b/billing-finance-core/.eslintrc.js deleted file mode 100644 index 09d5e2f..0000000 --- a/billing-finance-core/.eslintrc.js +++ /dev/null @@ -1,25 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - tsconfigRootDir: __dirname, - sourceType: 'module', - }, - plugins: ['@typescript-eslint/eslint-plugin'], - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', - ], - root: true, - env: { - node: true, - jest: true, - }, - ignorePatterns: ['.eslintrc.js'], - rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - }, -}; diff --git a/billing-finance-core/.gitignore b/billing-finance-core/.gitignore deleted file mode 100644 index 2033061..0000000 --- a/billing-finance-core/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules -dist -.env -*.log -.DS_Store -coverage diff --git a/billing-finance-core/BILLING-FINANCE-CORE.md b/billing-finance-core/BILLING-FINANCE-CORE.md deleted file mode 100644 index e5ea31a..0000000 --- a/billing-finance-core/BILLING-FINANCE-CORE.md +++ /dev/null @@ -1,102 +0,0 @@ -# BILLING-FINANCE-CORE - -Este microserviço é o coração financeiro da plataforma SaaS, responsável por gerenciar todo o ciclo de vida de assinaturas, cobranças (billing), emissão de notas fiscais (fiscal) e um CRM operacional para gestão de clientes e vendas. - -## 📋 Visão Geral - -O projeto foi construído pensando em **Multi-tenancy** desde o dia zero, utilizando **NestJS** para modularidade e **Prisma** para interação robusta com o banco de dados. - -### Arquitetura - -O diagrama abaixo ilustra a interação entre os componentes e serviços externos: - -```mermaid -graph TD - Client[Cliente/Frontend] -->|HTTP/REST| API[Billing Finance API] - API -->|Valida Token| Identity[Identity Gateway] - - subgraph Core Modules - API --> Tenants - API --> Plans - API --> Subscriptions - API --> Invoices - API --> Payments - API --> Fiscal - API --> CRM - end - - Payments -->|Webhook/API| Stripe[Stripe / Gateway] - Payments -->|Webhook/API| Boleto[Gerador Boleto] - Fiscal -->|NFS-e| NuvemFiscal[Nuvem Fiscal API] - - API --> DB[(PostgreSQL)] -``` - -## 🚀 Estrutura do Projeto - -A aplicação é dividida em módulos de domínio, cada um com responsabilidade única: - -| Módulo | Descrição | -| :--- | :--- | -| **Tenants** | Gestão dos clientes (empresas) que usam a plataforma. | -| **Plans** | Definição de planos, preços, ciclos (mensal/anual) e limites. | -| **Subscriptions** | Vínculo entre um Tenant e um Plan (Ciclo de Vida). | -| **Invoices** | Faturas geradas a partir das assinaturas (Contas a Receber). | -| **Payments** | Integração com gateways (Stripe, Boleto, Pix) e conciliação. | -| **Fiscal** | Emissão de Notas Fiscais de Serviço (NFS-e). | -| **CRM** | Gestão leve de empresas, contatos e oportunidades (deals). | - -## 🛠️ Tecnologias e Otimizações - -- **Backend**: Node.js 20 + NestJS -- **ORM**: Prisma (PostgreSQL) -- **Containerização**: - - Multi-stage builds (Builder + Prod Deps + Runtime). - - Runtime baseado em `gcr.io/distroless/nodejs20-debian12`. - - Execução segura sem shell e com usuário não-privilegiado (padrão distroless). - -## 💻 Como Executar - -O ambiente pode ser levantado facilmente via Docker Compose. - -### Pré-requisitos -- Docker & Docker Compose -- Node.js 20+ (para desenvolvimento local) - -### Passo a Passo - -1. **Configuração:** - Copie o arquivo de exemplo env: - ```bash - cp .env.example .env - ``` - -2. **Inicie os serviços:** - ```bash - docker-compose up --build - ``` - A API estará disponível na porta configurada (padrão `3000` ou similar). - -3. **Desenvolvimento Local:** - Se preferir rodar fora do Docker: - ```bash - npm install - npm run prisma:generate - npm run start:dev - ``` - -## 🔐 Segurança e Multi-tenancy - -O serviço opera em um modelo de confiança delegada: - -1. **JWT**: Não realiza login direto. Confia no cabeçalho `Authorization: Bearer ` validado pelo `Identity Gateway`. -2. **AuthGuard**: Decodifica o token para extrair `tenantId` e `userId`. -3. **Isolamento de Dados**: O `tenantId` é injetado obrigatoriamente em todas as operações do banco de dados para garantir que um cliente nunca acesse dados de outro. - -## 🔧 Detalhes do Dockerfile - -O `Dockerfile` foi otimizado para produção: - -- **Builder**: Compila o TypeScript e gera o Prisma Client. -- **Prod Deps**: Instala apenas dependências de produção (`--omit=dev`), reduzindo o tamanho da imagem. -- **Runtime (Distroless)**: Copia apenas o necessário (`dist`, `node_modules`, `prisma`) para uma imagem final minimalista e segura. diff --git a/billing-finance-core/Dockerfile b/billing-finance-core/Dockerfile deleted file mode 100644 index cd1a7ba..0000000 --- a/billing-finance-core/Dockerfile +++ /dev/null @@ -1,44 +0,0 @@ -# Dockerfile -# Stage 1: Build the application -FROM docker.io/library/node:20-alpine AS builder - -WORKDIR /usr/src/app - -COPY package*.json ./ -COPY tsconfig*.json ./ -COPY prisma ./prisma/ - -RUN npm ci -RUN npx prisma generate - -COPY . . - -RUN npm run build - -# Stage 2: Install production dependencies -FROM docker.io/library/node:20-alpine AS prod-deps - -WORKDIR /app - -COPY package.json package-lock.json* ./ -COPY prisma ./prisma - -# Install only production dependencies -# generating prisma client again is often needed if it relies on post-install scripts or binary positioning -RUN npm install --omit=dev -RUN npx prisma generate - -# Stage 3: Run the application -FROM gcr.io/distroless/nodejs20-debian12 - -WORKDIR /app - -ENV NODE_ENV=production - -# Copy necessary files from build stages -COPY --from=prod-deps /app/node_modules ./node_modules -COPY --from=builder /usr/src/app/dist ./dist -# Copy prisma folder might be needed for migrations or schema references -COPY --from=builder /usr/src/app/prisma ./prisma - -CMD ["dist/main.js"] diff --git a/billing-finance-core/docs/architecture.md b/billing-finance-core/docs/architecture.md deleted file mode 100644 index f17af20..0000000 --- a/billing-finance-core/docs/architecture.md +++ /dev/null @@ -1,18 +0,0 @@ -# Arquitetura do billing-finance-core - -## Visão geral -O serviço `billing-finance-core` é responsável pelo core financeiro, billing, fiscal e CRM da plataforma SaaS multi-tenant. Ele confia no `identity-gateway` para autenticação e recebe o `tenantId` via JWT interno. - -## Principais componentes -- **Core**: Guard de autenticação JWT e contexto de tenant. -- **Módulos de domínio**: tenants, planos, assinaturas, invoices, payments, fiscal e CRM. -- **Gateways de pagamento**: padrão Strategy para Pix, boleto e cartão. -- **Persistência**: PostgreSQL com Prisma e migrations. - -## Multi-tenant -- Todas as rotas usam `tenantId` extraído do JWT interno. -- Consultas sempre filtram por `tenantId`. - -## Integrações -- **Identity Gateway**: JWT interno contendo `tenantId`, `userId`, `roles`. -- **Gateways de pagamento**: integração via webhooks e reconciliação idempotente. diff --git a/billing-finance-core/docs/billing-flow.md b/billing-finance-core/docs/billing-flow.md deleted file mode 100644 index 3480d55..0000000 --- a/billing-finance-core/docs/billing-flow.md +++ /dev/null @@ -1,18 +0,0 @@ -# Fluxo de cobrança - -1. **Criação de plano** - - Define preço, ciclo de cobrança e limites. -2. **Assinatura** - - Relaciona tenant e plano, define datas de ciclo e status. -3. **Invoice** - - Conta a receber com vencimento e status. -4. **Pagamento** - - Gateway escolhido gera pagamento (Pix, boleto, cartão). -5. **Webhook** - - Gateway envia evento de pagamento. -6. **Conciliação** - - Atualiza status do pagamento e invoice. - -## Status principais -- **Invoice**: PENDING, PAID, OVERDUE, CANCELED -- **Payment**: PENDING, CONFIRMED, FAILED, CANCELED diff --git a/billing-finance-core/docs/fiscal.md b/billing-finance-core/docs/fiscal.md deleted file mode 100644 index caac2e4..0000000 --- a/billing-finance-core/docs/fiscal.md +++ /dev/null @@ -1,12 +0,0 @@ -# Fiscal (base) - -O módulo fiscal mantém informações básicas para emissão de NFS-e. - -## Campos -- Número da nota -- Status (DRAFT, ISSUED, CANCELED) -- Links de PDF/XML - -## Integração futura -- Preparado para integrar com provedor externo. -- Não inclui regras municipais complexas. diff --git a/billing-finance-core/nest-cli.json b/billing-finance-core/nest-cli.json deleted file mode 100644 index 56167b3..0000000 --- a/billing-finance-core/nest-cli.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "collection": "@nestjs/schematics", - "sourceRoot": "src" -} diff --git a/billing-finance-core/package-lock.json b/billing-finance-core/package-lock.json deleted file mode 100644 index 12c68df..0000000 --- a/billing-finance-core/package-lock.json +++ /dev/null @@ -1,6520 +0,0 @@ -{ - "name": "billing-finance-core", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "billing-finance-core", - "version": "1.0.0", - "dependencies": { - "@nestjs/axios": "^3.0.0", - "@nestjs/common": "^10.3.0", - "@nestjs/core": "^10.3.0", - "@nestjs/platform-express": "^10.3.0", - "@prisma/client": "^5.20.0", - "axios": "^1.6.0", - "class-transformer": "^0.5.1", - "class-validator": "^0.14.1", - "dotenv": "^16.4.5", - "jsonwebtoken": "^9.0.2", - "reflect-metadata": "^0.2.2", - "rxjs": "^7.8.1" - }, - "devDependencies": { - "@nestjs/cli": "^10.4.2", - "@nestjs/schematics": "^10.1.2", - "@nestjs/testing": "^10.3.0", - "@types/eslint": "^9.6.1", - "@types/jsonwebtoken": "^9.0.6", - "@types/node": "^20.14.11", - "@typescript-eslint/eslint-plugin": "^8.51.0", - "@typescript-eslint/parser": "^8.51.0", - "eslint": "^8.57.1", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.4", - "prisma": "^5.20.0", - "ts-node": "^10.9.2", - "typescript": "^5.5.4" - } - }, - "node_modules/@angular-devkit/core": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.11.tgz", - "integrity": "sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.1", - "picomatch": "4.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@angular-devkit/schematics": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.11.tgz", - "integrity": "sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "17.3.11", - "jsonc-parser": "3.2.1", - "magic-string": "0.30.8", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/schematics-cli": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-17.3.11.tgz", - "integrity": "sha512-kcOMqp+PHAKkqRad7Zd7PbpqJ0LqLaNZdY1+k66lLWmkEBozgq8v4ASn/puPWf9Bo0HpCiK+EzLf0VHE8Z/y6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "17.3.11", - "@angular-devkit/schematics": "17.3.11", - "ansi-colors": "4.1.3", - "inquirer": "9.2.15", - "symbol-observable": "4.0.0", - "yargs-parser": "21.1.1" - }, - "bin": { - "schematics": "bin/schematics.js" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/schematics-cli/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@angular-devkit/schematics-cli/node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/@angular-devkit/schematics-cli/node_modules/inquirer": { - "version": "9.2.15", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.15.tgz", - "integrity": "sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ljharb/through": "^2.3.12", - "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "figures": "^3.2.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", - "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular-devkit/schematics-cli/node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@angular-devkit/schematics-cli/node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@borewit/text-codec": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", - "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@ljharb/through": { - "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.14.tgz", - "integrity": "sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/@lukeed/csprng": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", - "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@nestjs/axios": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.1.3.tgz", - "integrity": "sha512-RZ/63c1tMxGLqyG3iOCVt7A72oy4x1eM6QEhd4KzCYpaVWW0igq0WSREeRoEZhIxRcZfDfIIkvsOMiM7yfVGZQ==", - "license": "MIT", - "peerDependencies": { - "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", - "axios": "^1.3.1", - "rxjs": "^6.0.0 || ^7.0.0" - } - }, - "node_modules/@nestjs/cli": { - "version": "10.4.9", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.9.tgz", - "integrity": "sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "17.3.11", - "@angular-devkit/schematics": "17.3.11", - "@angular-devkit/schematics-cli": "17.3.11", - "@nestjs/schematics": "^10.0.1", - "chalk": "4.1.2", - "chokidar": "3.6.0", - "cli-table3": "0.6.5", - "commander": "4.1.1", - "fork-ts-checker-webpack-plugin": "9.0.2", - "glob": "10.4.5", - "inquirer": "8.2.6", - "node-emoji": "1.11.0", - "ora": "5.4.1", - "tree-kill": "1.2.2", - "tsconfig-paths": "4.2.0", - "tsconfig-paths-webpack-plugin": "4.2.0", - "typescript": "5.7.2", - "webpack": "5.97.1", - "webpack-node-externals": "3.0.0" - }, - "bin": { - "nest": "bin/nest.js" - }, - "engines": { - "node": ">= 16.14" - }, - "peerDependencies": { - "@swc/cli": "^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0", - "@swc/core": "^1.3.62" - }, - "peerDependenciesMeta": { - "@swc/cli": { - "optional": true - }, - "@swc/core": { - "optional": true - } - } - }, - "node_modules/@nestjs/cli/node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@nestjs/common": { - "version": "10.4.20", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.20.tgz", - "integrity": "sha512-hxJxZF7jcKGuUzM9EYbuES80Z/36piJbiqmPy86mk8qOn5gglFebBTvcx7PWVbRNSb4gngASYnefBj/Y2HAzpQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "file-type": "20.4.1", - "iterare": "1.2.1", - "tslib": "2.8.1", - "uid": "2.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" - }, - "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", - "reflect-metadata": "^0.1.12 || ^0.2.0", - "rxjs": "^7.1.0" - }, - "peerDependenciesMeta": { - "class-transformer": { - "optional": true - }, - "class-validator": { - "optional": true - } - } - }, - "node_modules/@nestjs/core": { - "version": "10.4.20", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.20.tgz", - "integrity": "sha512-kRdtyKA3+Tu70N3RQ4JgmO1E3LzAMs/eppj7SfjabC7TgqNWoS4RLhWl4BqmsNVmjj6D5jgfPVtHtgYkU3AfpQ==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@nuxtjs/opencollective": "0.3.2", - "fast-safe-stringify": "2.1.1", - "iterare": "1.2.1", - "path-to-regexp": "3.3.0", - "tslib": "2.8.1", - "uid": "2.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" - }, - "peerDependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/microservices": "^10.0.0", - "@nestjs/platform-express": "^10.0.0", - "@nestjs/websockets": "^10.0.0", - "reflect-metadata": "^0.1.12 || ^0.2.0", - "rxjs": "^7.1.0" - }, - "peerDependenciesMeta": { - "@nestjs/microservices": { - "optional": true - }, - "@nestjs/platform-express": { - "optional": true - }, - "@nestjs/websockets": { - "optional": true - } - } - }, - "node_modules/@nestjs/platform-express": { - "version": "10.4.20", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.20.tgz", - "integrity": "sha512-rh97mX3rimyf4xLMLHuTOBKe6UD8LOJ14VlJ1F/PTd6C6ZK9Ak6EHuJvdaGcSFQhd3ZMBh3I6CuujKGW9pNdIg==", - "license": "MIT", - "peer": true, - "dependencies": { - "body-parser": "1.20.3", - "cors": "2.8.5", - "express": "4.21.2", - "multer": "2.0.2", - "tslib": "2.8.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" - }, - "peerDependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/core": "^10.0.0" - } - }, - "node_modules/@nestjs/schematics": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.3.tgz", - "integrity": "sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "17.3.11", - "@angular-devkit/schematics": "17.3.11", - "comment-json": "4.2.5", - "jsonc-parser": "3.3.1", - "pluralize": "8.0.0" - }, - "peerDependencies": { - "typescript": ">=4.8.2" - } - }, - "node_modules/@nestjs/schematics/node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@nestjs/testing": { - "version": "10.4.20", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.20.tgz", - "integrity": "sha512-nMkRDukDKskdPruM6EsgMq7yJua+CPZM6I6FrLP8yXw8BiVSPv9Nm0CtcGGwt3kgZF9hfxKjGqLjsvVBsv6Vfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" - }, - "peerDependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/core": "^10.0.0", - "@nestjs/microservices": "^10.0.0", - "@nestjs/platform-express": "^10.0.0" - }, - "peerDependenciesMeta": { - "@nestjs/microservices": { - "optional": true - }, - "@nestjs/platform-express": { - "optional": true - } - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nuxtjs/opencollective": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", - "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "consola": "^2.15.0", - "node-fetch": "^2.6.1" - }, - "bin": { - "opencollective": "bin/opencollective.js" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@prisma/client": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", - "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", - "hasInstallScript": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.13" - }, - "peerDependencies": { - "prisma": "*" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - } - } - }, - "node_modules/@prisma/debug": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", - "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", - "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/fetch-engine": "5.22.0", - "@prisma/get-platform": "5.22.0" - } - }, - "node_modules/@prisma/engines-version": { - "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", - "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/fetch-engine": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", - "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/get-platform": "5.22.0" - } - }, - "node_modules/@prisma/get-platform": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", - "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.22.0" - } - }, - "node_modules/@tokenizer/inflate": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", - "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "fflate": "^0.8.2", - "token-types": "^6.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/inflate/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@tokenizer/inflate/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "license": "MIT" - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*", - "@types/node": "*" - } - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", - "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/validator": { - "version": "13.15.10", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", - "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", - "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.51.0", - "@typescript-eslint/type-utils": "8.51.0", - "@typescript-eslint/utils": "8.51.0", - "@typescript-eslint/visitor-keys": "8.51.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.51.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz", - "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.51.0", - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/typescript-estree": "8.51.0", - "@typescript-eslint/visitor-keys": "8.51.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", - "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.51.0", - "@typescript-eslint/types": "^8.51.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/project-service/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz", - "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/visitor-keys": "8.51.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", - "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz", - "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/typescript-estree": "8.51.0", - "@typescript-eslint/utils": "8.51.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/types": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", - "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", - "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.51.0", - "@typescript-eslint/tsconfig-utils": "8.51.0", - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/visitor-keys": "8.51.0", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz", - "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.51.0", - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/typescript-estree": "8.51.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", - "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.51.0", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", - "license": "MIT" - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/array-timsort": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", - "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", - "license": "MIT", - "peer": true, - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.11", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", - "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001762", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", - "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true, - "license": "MIT" - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/class-transformer": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT", - "peer": true - }, - "node_modules/class-validator": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz", - "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/validator": "^13.15.3", - "libphonenumber-js": "^1.11.1", - "validator": "^13.15.20" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/comment-json": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", - "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-timsort": "^1.0.3", - "core-util-is": "^1.0.3", - "esprima": "^4.0.1", - "has-own-prop": "^2.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/consola": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.18.4", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", - "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", - "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "license": "MIT" - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-type": { - "version": "20.4.1", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.4.1.tgz", - "integrity": "sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==", - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.2.6", - "strtok3": "^10.2.0", - "token-types": "^6.0.0", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fork-ts-checker-webpack-plugin": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz", - "integrity": "sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.16.7", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "cosmiconfig": "^8.2.0", - "deepmerge": "^4.2.2", - "fs-extra": "^10.0.0", - "memfs": "^3.4.1", - "minimatch": "^3.0.4", - "node-abort-controller": "^3.0.1", - "schema-utils": "^3.1.1", - "semver": "^7.3.5", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">=12.13.0", - "yarn": ">=1.0.0" - }, - "peerDependencies": { - "typescript": ">3.6.0", - "webpack": "^5.11.0" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-monkey": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", - "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", - "dev": true, - "license": "Unlicense" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-own-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", - "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/iterare": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", - "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", - "license": "ISC", - "engines": { - "node": ">=6" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", - "license": "MIT", - "dependencies": { - "jws": "^4.0.1", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/libphonenumber-js": { - "version": "1.12.33", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.33.tgz", - "integrity": "sha512-r9kw4OA6oDO4dPXkOrXTkArQAafIKAU71hChInV4FxZ69dxCfbwQGDPzqR5/vea94wU705/3AZroEbSoeVWrQw==", - "license": "MIT" - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "dev": true, - "license": "Unlicense", - "dependencies": { - "fs-monkey": "^1.0.4" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/multer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", - "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", - "license": "MIT", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.6.0", - "concat-stream": "^2.0.0", - "mkdirp": "^0.5.6", - "object-assign": "^4.1.1", - "type-is": "^1.6.18", - "xtend": "^4.0.2" - }, - "engines": { - "node": ">= 10.16.0" - } - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true, - "license": "ISC" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-abort-controller": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.21" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-to-regexp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", - "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", - "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/prisma": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", - "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@prisma/engines": "5.22.0" - }, - "bin": { - "prisma": "build/index.js" - }, - "engines": { - "node": ">=16.13" - }, - "optionalDependencies": { - "fsevents": "2.3.3" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0", - "peer": true - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strtok3": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/symbol-observable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", - "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser": { - "version": "5.44.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", - "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/token-types": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", - "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.1.0", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-api-utils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz", - "integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tsconfig-paths-webpack-plugin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", - "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.7.0", - "tapable": "^2.2.1", - "tsconfig-paths": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uid": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", - "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", - "license": "MIT", - "dependencies": { - "@lukeed/csprng": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/validator": { - "version": "13.15.26", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", - "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/watchpack": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.0.tgz", - "integrity": "sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/webpack": { - "version": "5.97.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", - "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-node-externals": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", - "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/billing-finance-core/package.json b/billing-finance-core/package.json deleted file mode 100644 index fafef17..0000000 --- a/billing-finance-core/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "billing-finance-core", - "version": "1.0.0", - "description": "Core financeiro, billing, fiscal e CRM para plataforma SaaS multi-tenant.", - "main": "dist/main.js", - "scripts": { - "build": "nest build", - "start": "nest start", - "start:dev": "nest start --watch", - "start:prod": "node dist/main.js", - "lint": "eslint \"src/**/*.ts\"", - "prisma:generate": "prisma generate", - "prisma:migrate": "prisma migrate deploy", - "prisma:studio": "prisma studio" - }, - "dependencies": { - "@nestjs/axios": "^3.0.0", - "@nestjs/common": "^10.3.0", - "@nestjs/core": "^10.3.0", - "@nestjs/platform-express": "^10.3.0", - "@prisma/client": "^5.20.0", - "axios": "^1.6.0", - "class-transformer": "^0.5.1", - "class-validator": "^0.14.1", - "dotenv": "^16.4.5", - "jsonwebtoken": "^9.0.2", - "reflect-metadata": "^0.2.2", - "rxjs": "^7.8.1" - }, - "devDependencies": { - "@nestjs/cli": "^10.4.2", - "@nestjs/schematics": "^10.1.2", - "@nestjs/testing": "^10.3.0", - "@types/eslint": "^9.6.1", - "@types/jsonwebtoken": "^9.0.6", - "@types/node": "^20.14.11", - "@typescript-eslint/eslint-plugin": "^8.51.0", - "@typescript-eslint/parser": "^8.51.0", - "eslint": "^8.57.1", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.4", - "prisma": "^5.20.0", - "ts-node": "^10.9.2", - "typescript": "^5.5.4" - } -} diff --git a/billing-finance-core/prisma/migrations/000_init/migration.sql b/billing-finance-core/prisma/migrations/000_init/migration.sql deleted file mode 100644 index c677820..0000000 --- a/billing-finance-core/prisma/migrations/000_init/migration.sql +++ /dev/null @@ -1,166 +0,0 @@ --- CreateEnum -CREATE TYPE "BillingCycle" AS ENUM ('MONTHLY', 'YEARLY'); -CREATE TYPE "SubscriptionStatus" AS ENUM ('ACTIVE', 'PAST_DUE', 'CANCELED', 'TRIAL'); -CREATE TYPE "InvoiceStatus" AS ENUM ('PENDING', 'PAID', 'OVERDUE', 'CANCELED'); -CREATE TYPE "PaymentStatus" AS ENUM ('PENDING', 'CONFIRMED', 'FAILED', 'CANCELED'); -CREATE TYPE "FiscalStatus" AS ENUM ('DRAFT', 'ISSUED', 'CANCELED'); -CREATE TYPE "DealStage" AS ENUM ('LEAD', 'PROPOSAL', 'NEGOTIATION', 'WON', 'LOST'); - --- CreateTable -CREATE TABLE "Tenant" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "taxId" TEXT, - "status" TEXT NOT NULL DEFAULT 'ACTIVE', - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Tenant_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Plan" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "priceCents" INTEGER NOT NULL, - "billingCycle" "BillingCycle" NOT NULL, - "softLimit" INTEGER, - "hardLimit" INTEGER, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Plan_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Subscription" ( - "id" TEXT NOT NULL, - "tenantId" TEXT NOT NULL, - "planId" TEXT NOT NULL, - "status" "SubscriptionStatus" NOT NULL DEFAULT 'ACTIVE', - "startDate" TIMESTAMP(3) NOT NULL, - "nextDueDate" TIMESTAMP(3) NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Subscription_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Invoice" ( - "id" TEXT NOT NULL, - "tenantId" TEXT NOT NULL, - "subscriptionId" TEXT, - "amountCents" INTEGER NOT NULL, - "dueDate" TIMESTAMP(3) NOT NULL, - "status" "InvoiceStatus" NOT NULL DEFAULT 'PENDING', - "paidAt" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Invoice_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Payment" ( - "id" TEXT NOT NULL, - "tenantId" TEXT NOT NULL, - "invoiceId" TEXT NOT NULL, - "gateway" TEXT NOT NULL, - "method" TEXT NOT NULL, - "externalId" TEXT, - "status" "PaymentStatus" NOT NULL DEFAULT 'PENDING', - "amountCents" INTEGER NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Payment_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "FiscalDocument" ( - "id" TEXT NOT NULL, - "tenantId" TEXT NOT NULL, - "invoiceId" TEXT, - "number" TEXT, - "status" "FiscalStatus" NOT NULL DEFAULT 'DRAFT', - "pdfUrl" TEXT, - "xmlUrl" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "FiscalDocument_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "CrmCompany" ( - "id" TEXT NOT NULL, - "tenantId" TEXT NOT NULL, - "name" TEXT NOT NULL, - "segment" TEXT, - "website" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "CrmCompany_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "CrmContact" ( - "id" TEXT NOT NULL, - "tenantId" TEXT NOT NULL, - "companyId" TEXT, - "name" TEXT NOT NULL, - "email" TEXT, - "phone" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "CrmContact_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "CrmDeal" ( - "id" TEXT NOT NULL, - "tenantId" TEXT NOT NULL, - "companyId" TEXT, - "name" TEXT NOT NULL, - "stage" "DealStage" NOT NULL DEFAULT 'LEAD', - "valueCents" INTEGER NOT NULL, - "expectedAt" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "CrmDeal_pkey" PRIMARY KEY ("id") -); - --- Indexes -CREATE INDEX "Subscription_tenantId_idx" ON "Subscription"("tenantId"); -CREATE INDEX "Subscription_planId_idx" ON "Subscription"("planId"); -CREATE INDEX "Invoice_tenantId_idx" ON "Invoice"("tenantId"); -CREATE INDEX "Invoice_subscriptionId_idx" ON "Invoice"("subscriptionId"); -CREATE INDEX "Payment_tenantId_idx" ON "Payment"("tenantId"); -CREATE INDEX "Payment_invoiceId_idx" ON "Payment"("invoiceId"); -CREATE UNIQUE INDEX "Payment_gateway_externalId_key" ON "Payment"("gateway", "externalId"); -CREATE INDEX "FiscalDocument_tenantId_idx" ON "FiscalDocument"("tenantId"); -CREATE INDEX "FiscalDocument_invoiceId_idx" ON "FiscalDocument"("invoiceId"); -CREATE INDEX "CrmCompany_tenantId_idx" ON "CrmCompany"("tenantId"); -CREATE INDEX "CrmContact_tenantId_idx" ON "CrmContact"("tenantId"); -CREATE INDEX "CrmContact_companyId_idx" ON "CrmContact"("companyId"); -CREATE INDEX "CrmDeal_tenantId_idx" ON "CrmDeal"("tenantId"); -CREATE INDEX "CrmDeal_companyId_idx" ON "CrmDeal"("companyId"); - --- Foreign Keys -ALTER TABLE "Subscription" ADD CONSTRAINT "Subscription_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE "Subscription" ADD CONSTRAINT "Subscription_planId_fkey" FOREIGN KEY ("planId") REFERENCES "Plan"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE "Invoice" ADD CONSTRAINT "Invoice_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE "Invoice" ADD CONSTRAINT "Invoice_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "Subscription"("id") ON DELETE SET NULL ON UPDATE CASCADE; -ALTER TABLE "Payment" ADD CONSTRAINT "Payment_invoiceId_fkey" FOREIGN KEY ("invoiceId") REFERENCES "Invoice"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE "Payment" ADD CONSTRAINT "Payment_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE "FiscalDocument" ADD CONSTRAINT "FiscalDocument_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE "FiscalDocument" ADD CONSTRAINT "FiscalDocument_invoiceId_fkey" FOREIGN KEY ("invoiceId") REFERENCES "Invoice"("id") ON DELETE SET NULL ON UPDATE CASCADE; -ALTER TABLE "CrmCompany" ADD CONSTRAINT "CrmCompany_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE "CrmContact" ADD CONSTRAINT "CrmContact_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE "CrmContact" ADD CONSTRAINT "CrmContact_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "CrmCompany"("id") ON DELETE SET NULL ON UPDATE CASCADE; -ALTER TABLE "CrmDeal" ADD CONSTRAINT "CrmDeal_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE "CrmDeal" ADD CONSTRAINT "CrmDeal_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "CrmCompany"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/billing-finance-core/prisma/schema.prisma b/billing-finance-core/prisma/schema.prisma deleted file mode 100644 index 721d276..0000000 --- a/billing-finance-core/prisma/schema.prisma +++ /dev/null @@ -1,196 +0,0 @@ -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") -} - -enum BillingCycle { - MONTHLY - YEARLY -} - -enum SubscriptionStatus { - ACTIVE - PAST_DUE - CANCELED - TRIAL -} - -enum InvoiceStatus { - PENDING - PAID - OVERDUE - CANCELED -} - -enum PaymentStatus { - PENDING - CONFIRMED - FAILED - CANCELED -} - -enum FiscalStatus { - DRAFT - ISSUED - CANCELED -} - -enum DealStage { - LEAD - PROPOSAL - NEGOTIATION - WON - LOST -} - -model Tenant { - id String @id @default(uuid()) - name String - taxId String? - status String @default("ACTIVE") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - subscriptions Subscription[] - invoices Invoice[] - payments Payment[] - fiscalDocs FiscalDocument[] - crmCompanies CrmCompany[] - crmContacts CrmContact[] - crmDeals CrmDeal[] -} - -model Plan { - id String @id @default(uuid()) - name String - priceCents Int - billingCycle BillingCycle - softLimit Int? - hardLimit Int? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - subscriptions Subscription[] -} - -model Subscription { - id String @id @default(uuid()) - tenantId String - planId String - status SubscriptionStatus @default(ACTIVE) - startDate DateTime - nextDueDate DateTime - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - tenant Tenant @relation(fields: [tenantId], references: [id]) - plan Plan @relation(fields: [planId], references: [id]) - invoices Invoice[] - - @@index([tenantId]) - @@index([planId]) -} - -model Invoice { - id String @id @default(uuid()) - tenantId String - subscriptionId String? - amountCents Int - dueDate DateTime - status InvoiceStatus @default(PENDING) - paidAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - tenant Tenant @relation(fields: [tenantId], references: [id]) - subscription Subscription? @relation(fields: [subscriptionId], references: [id]) - payments Payment[] - fiscalDocs FiscalDocument[] - - @@index([tenantId]) - @@index([subscriptionId]) -} - -model Payment { - id String @id @default(uuid()) - tenantId String - invoiceId String - gateway String - method String - externalId String? - status PaymentStatus @default(PENDING) - amountCents Int - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - invoice Invoice @relation(fields: [invoiceId], references: [id]) - tenant Tenant @relation(fields: [tenantId], references: [id]) - - @@index([tenantId]) - @@index([invoiceId]) - @@unique([gateway, externalId]) -} - -model FiscalDocument { - id String @id @default(uuid()) - tenantId String - invoiceId String? - number String? - status FiscalStatus @default(DRAFT) - pdfUrl String? - xmlUrl String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - tenant Tenant @relation(fields: [tenantId], references: [id]) - invoice Invoice? @relation(fields: [invoiceId], references: [id]) - - @@index([tenantId]) - @@index([invoiceId]) -} - -model CrmCompany { - id String @id @default(uuid()) - tenantId String - name String - segment String? - website String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - tenant Tenant @relation(fields: [tenantId], references: [id]) - contacts CrmContact[] - deals CrmDeal[] - - @@index([tenantId]) -} - -model CrmContact { - id String @id @default(uuid()) - tenantId String - companyId String? - name String - email String? - phone String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - tenant Tenant @relation(fields: [tenantId], references: [id]) - company CrmCompany? @relation(fields: [companyId], references: [id]) - - @@index([tenantId]) - @@index([companyId]) -} - -model CrmDeal { - id String @id @default(uuid()) - tenantId String - companyId String? - name String - stage DealStage @default(LEAD) - valueCents Int - expectedAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - tenant Tenant @relation(fields: [tenantId], references: [id]) - company CrmCompany? @relation(fields: [companyId], references: [id]) - - @@index([tenantId]) - @@index([companyId]) -} diff --git a/billing-finance-core/src/app.controller.ts b/billing-finance-core/src/app.controller.ts deleted file mode 100644 index 8765d7e..0000000 --- a/billing-finance-core/src/app.controller.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; - -@Controller() -export class AppController { - @Get('health') - getHealth() { - return { status: 'ok' }; - } -} diff --git a/billing-finance-core/src/app.module.ts b/billing-finance-core/src/app.module.ts deleted file mode 100644 index 8a238ed..0000000 --- a/billing-finance-core/src/app.module.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Module } from '@nestjs/common'; -import { APP_GUARD } from '@nestjs/core'; -import { AppController } from './app.controller'; -import { AuthGuard } from './core/auth.guard'; -import { PrismaService } from './lib/postgres'; -import { TenantModule } from './modules/tenants/tenant.module'; -import { PlanModule } from './modules/plans/plan.module'; -import { SubscriptionModule } from './modules/subscriptions/subscription.module'; -import { InvoiceModule } from './modules/invoices/invoice.module'; -import { PaymentModule } from './modules/payments/payment.module'; -import { WebhookModule } from './modules/webhooks/webhook.module'; -import { FiscalModule } from './modules/fiscal/fiscal.module'; -import { CrmModule } from './modules/crm/crm.module'; - -@Module({ - controllers: [AppController], - imports: [ - TenantModule, - PlanModule, - SubscriptionModule, - InvoiceModule, - PaymentModule, - WebhookModule, - FiscalModule, - CrmModule, - ], - providers: [ - PrismaService, - { - provide: APP_GUARD, - useClass: AuthGuard, - }, - ], -}) -export class AppModule {} diff --git a/billing-finance-core/src/core/auth.guard.ts b/billing-finance-core/src/core/auth.guard.ts deleted file mode 100644 index b1820c3..0000000 --- a/billing-finance-core/src/core/auth.guard.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - CanActivate, - ExecutionContext, - Injectable, - UnauthorizedException, -} from '@nestjs/common'; -import { Request } from 'express'; -import jwt, { JwtPayload } from 'jsonwebtoken'; -import { env } from '../lib/env'; - -interface IdentityGatewayPayload extends JwtPayload { - tenantId: string; - userId: string; - roles: string[]; -} - -@Injectable() -export class AuthGuard implements CanActivate { - canActivate(context: ExecutionContext): boolean { - const request = context.switchToHttp().getRequest(); - if (request.path.startsWith('/webhooks')) { - const secret = request.headers['x-webhook-secret']; - if (secret && secret === env.webhookSecret) { - return true; - } - } - const authHeader = request.headers.authorization; - - if (!authHeader?.startsWith('Bearer ')) { - throw new UnauthorizedException('Missing bearer token'); - } - - const token = authHeader.replace('Bearer ', '').trim(); - try { - const verified = jwt.verify( - token, - env.jwtPublicKey || env.jwtSecret, - { - issuer: env.jwtIssuer, - }, - ) as IdentityGatewayPayload; - - if (!verified.tenantId || !verified.userId) { - throw new UnauthorizedException('Token missing tenant/user'); - } - - request.user = { - tenantId: verified.tenantId, - userId: verified.userId, - roles: verified.roles ?? [], - } as IdentityGatewayPayload; - - return true; - } catch (error) { - throw new UnauthorizedException('Invalid token'); - } - } -} diff --git a/billing-finance-core/src/core/enums.ts b/billing-finance-core/src/core/enums.ts deleted file mode 100644 index 34cf3b4..0000000 --- a/billing-finance-core/src/core/enums.ts +++ /dev/null @@ -1,39 +0,0 @@ -export enum BillingCycle { - MONTHLY = 'MONTHLY', - YEARLY = 'YEARLY', -} - -export enum SubscriptionStatus { - ACTIVE = 'ACTIVE', - PAST_DUE = 'PAST_DUE', - CANCELED = 'CANCELED', - TRIAL = 'TRIAL', -} - -export enum InvoiceStatus { - PENDING = 'PENDING', - PAID = 'PAID', - OVERDUE = 'OVERDUE', - CANCELED = 'CANCELED', -} - -export enum PaymentStatus { - PENDING = 'PENDING', - CONFIRMED = 'CONFIRMED', - FAILED = 'FAILED', - CANCELED = 'CANCELED', -} - -export enum FiscalStatus { - DRAFT = 'DRAFT', - ISSUED = 'ISSUED', - CANCELED = 'CANCELED', -} - -export enum DealStage { - LEAD = 'LEAD', - PROPOSAL = 'PROPOSAL', - NEGOTIATION = 'NEGOTIATION', - WON = 'WON', - LOST = 'LOST', -} diff --git a/billing-finance-core/src/core/tenant.context.ts b/billing-finance-core/src/core/tenant.context.ts deleted file mode 100644 index c58197b..0000000 --- a/billing-finance-core/src/core/tenant.context.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Request } from 'express'; - -export interface TenantContextPayload { - tenantId: string; - userId: string; - roles: string[]; -} - -export const getTenantContext = (request: Request): TenantContextPayload => { - const user = request.user as TenantContextPayload | undefined; - if (!user) { - throw new Error('Tenant context missing from request.'); - } - return user; -}; diff --git a/billing-finance-core/src/database/prisma.schema b/billing-finance-core/src/database/prisma.schema deleted file mode 100644 index 46fb117..0000000 --- a/billing-finance-core/src/database/prisma.schema +++ /dev/null @@ -1,2 +0,0 @@ -// Source of truth for database schema is /prisma/schema.prisma -// This file documents the required structure inside src/database. diff --git a/billing-finance-core/src/lib/env.ts b/billing-finance-core/src/lib/env.ts deleted file mode 100644 index 0f87600..0000000 --- a/billing-finance-core/src/lib/env.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as dotenv from 'dotenv'; - -dotenv.config(); - -export const env = { - nodeEnv: process.env.NODE_ENV ?? 'development', - port: Number(process.env.PORT ?? 3000), - databaseUrl: process.env.DATABASE_URL ?? '', - jwtSecret: process.env.JWT_SECRET ?? '', - jwtPublicKey: process.env.JWT_PUBLIC_KEY ?? '', - jwtIssuer: process.env.JWT_ISSUER ?? 'identity-gateway', - webhookSecret: process.env.PAYMENT_WEBHOOK_SECRET ?? '', - stripeApiKey: process.env.STRIPE_API_KEY ?? '', - appLogLevel: process.env.APP_LOG_LEVEL ?? 'info', -}; diff --git a/billing-finance-core/src/lib/logger.ts b/billing-finance-core/src/lib/logger.ts deleted file mode 100644 index 0598917..0000000 --- a/billing-finance-core/src/lib/logger.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Logger } from '@nestjs/common'; - -export const appLogger = new Logger('billing-finance-core'); diff --git a/billing-finance-core/src/lib/postgres.ts b/billing-finance-core/src/lib/postgres.ts deleted file mode 100644 index 623d5e0..0000000 --- a/billing-finance-core/src/lib/postgres.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; -import { PrismaClient } from '@prisma/client'; - -@Injectable() -export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { - async onModuleInit() { - await this.$connect(); - } - - async onModuleDestroy() { - await this.$disconnect(); - } -} diff --git a/billing-finance-core/src/main.ts b/billing-finance-core/src/main.ts deleted file mode 100644 index 19fb8b7..0000000 --- a/billing-finance-core/src/main.ts +++ /dev/null @@ -1,15 +0,0 @@ -import 'reflect-metadata'; -import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; -import { AppModule } from './app.module'; -import { env } from './lib/env'; -import { appLogger } from './lib/logger'; - -async function bootstrap() { - const app = await NestFactory.create(AppModule, { logger: appLogger }); - app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })); - await app.listen(env.port); - appLogger.log(`billing-finance-core running on port ${env.port}`); -} - -bootstrap(); diff --git a/billing-finance-core/src/modules/crm/README.md b/billing-finance-core/src/modules/crm/README.md deleted file mode 100644 index 59b8713..0000000 --- a/billing-finance-core/src/modules/crm/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# CRM Module - -Este módulo gerencia o relacionamento com clientes dentro do `billing-finance-core`. -**Atenção:** Existe um outro serviço chamado `crm-core` escrito em Go. Este módulo aqui serve como um CRM leve e integrado diretamente ao financeiro para facilitar a gestão de clientes que pagam faturas. - -## Responsabilidades -- Gerenciar Empresas (Companies) -- Gerenciar Contatos (Contacts) -- Pipeline de Vendas (Deals) simplificado - -## Multi-tenancy -- Todos os dados são isolados por `tenantId`. -- O `CrmService` exige `tenantId` em todas as operações de busca e criação. - -## Estrutura -- `CrmController`: Expõe endpoints REST. -- `CrmService`: Lógica de negócio e acesso ao banco via Prisma. diff --git a/billing-finance-core/src/modules/crm/company.entity.ts b/billing-finance-core/src/modules/crm/company.entity.ts deleted file mode 100644 index 481a070..0000000 --- a/billing-finance-core/src/modules/crm/company.entity.ts +++ /dev/null @@ -1,9 +0,0 @@ -export class CompanyEntity { - id: string; - tenantId: string; - name: string; - segment?: string; - website?: string; - createdAt: Date; - updatedAt: Date; -} diff --git a/billing-finance-core/src/modules/crm/contact.entity.ts b/billing-finance-core/src/modules/crm/contact.entity.ts deleted file mode 100644 index f235567..0000000 --- a/billing-finance-core/src/modules/crm/contact.entity.ts +++ /dev/null @@ -1,10 +0,0 @@ -export class ContactEntity { - id: string; - tenantId: string; - companyId?: string; - name: string; - email?: string; - phone?: string; - createdAt: Date; - updatedAt: Date; -} diff --git a/billing-finance-core/src/modules/crm/crm.controller.ts b/billing-finance-core/src/modules/crm/crm.controller.ts deleted file mode 100644 index 7629f5c..0000000 --- a/billing-finance-core/src/modules/crm/crm.controller.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Body, Controller, Get, Post, Req } from '@nestjs/common'; -import { IsDateString, IsEnum, IsInt, IsOptional, IsString, Min } from 'class-validator'; -import { Request } from 'express'; -import { DealStage } from '../../core/enums'; -import { getTenantContext } from '../../core/tenant.context'; -import { CrmService } from './crm.service'; - -class CreateCompanyDto { - @IsString() - name: string; - - @IsOptional() - @IsString() - segment?: string; - - @IsOptional() - @IsString() - website?: string; -} - -class CreateContactDto { - @IsString() - name: string; - - @IsOptional() - @IsString() - companyId?: string; - - @IsOptional() - @IsString() - email?: string; - - @IsOptional() - @IsString() - phone?: string; -} - -class CreateDealDto { - @IsString() - name: string; - - @IsOptional() - @IsString() - companyId?: string; - - @IsOptional() - @IsEnum(DealStage) - stage?: DealStage; - - @IsInt() - @Min(0) - valueCents: number; - - @IsOptional() - @IsDateString() - expectedAt?: string; -} - -@Controller('crm') -export class CrmController { - constructor(private readonly crmService: CrmService) {} - - @Get('companies') - listCompanies(@Req() req: Request) { - const { tenantId } = getTenantContext(req); - return this.crmService.listCompanies(tenantId); - } - - @Post('companies') - createCompany(@Req() req: Request, @Body() body: CreateCompanyDto) { - const { tenantId } = getTenantContext(req); - return this.crmService.createCompany({ tenantId, ...body }); - } - - @Post('contacts') - createContact(@Req() req: Request, @Body() body: CreateContactDto) { - const { tenantId } = getTenantContext(req); - return this.crmService.createContact({ tenantId, ...body }); - } - - @Post('deals') - createDeal(@Req() req: Request, @Body() body: CreateDealDto) { - const { tenantId } = getTenantContext(req); - return this.crmService.createDeal({ - tenantId, - companyId: body.companyId, - name: body.name, - stage: body.stage, - valueCents: body.valueCents, - expectedAt: body.expectedAt ? new Date(body.expectedAt) : undefined, - }); - } -} diff --git a/billing-finance-core/src/modules/crm/crm.module.ts b/billing-finance-core/src/modules/crm/crm.module.ts deleted file mode 100644 index 8400f46..0000000 --- a/billing-finance-core/src/modules/crm/crm.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { CrmController } from './crm.controller'; -import { CrmService } from './crm.service'; -import { PrismaService } from '../../lib/postgres'; - -@Module({ - controllers: [CrmController], - providers: [CrmService, PrismaService], - exports: [CrmService], -}) -export class CrmModule {} diff --git a/billing-finance-core/src/modules/crm/crm.service.ts b/billing-finance-core/src/modules/crm/crm.service.ts deleted file mode 100644 index 0437638..0000000 --- a/billing-finance-core/src/modules/crm/crm.service.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { DealStage } from '../../core/enums'; -import { PrismaService } from '../../lib/postgres'; - -interface CreateCompanyInput { - tenantId: string; - name: string; - segment?: string; - website?: string; -} - -interface CreateContactInput { - tenantId: string; - companyId?: string; - name: string; - email?: string; - phone?: string; -} - -interface CreateDealInput { - tenantId: string; - companyId?: string; - name: string; - stage?: DealStage; - valueCents: number; - expectedAt?: Date; -} - -@Injectable() -export class CrmService { - constructor(private readonly prisma: PrismaService) {} - - createCompany(data: CreateCompanyInput) { - return this.prisma.crmCompany.create({ data }); - } - - listCompanies(tenantId: string) { - return this.prisma.crmCompany.findMany({ - where: { tenantId }, - orderBy: { createdAt: 'desc' }, - }); - } - - createContact(data: CreateContactInput) { - return this.prisma.crmContact.create({ data }); - } - - createDeal(data: CreateDealInput) { - return this.prisma.crmDeal.create({ - data: { - tenantId: data.tenantId, - companyId: data.companyId, - name: data.name, - stage: data.stage ?? DealStage.LEAD, - valueCents: data.valueCents, - expectedAt: data.expectedAt, - }, - }); - } -} diff --git a/billing-finance-core/src/modules/crm/deal.entity.ts b/billing-finance-core/src/modules/crm/deal.entity.ts deleted file mode 100644 index 29f8e4f..0000000 --- a/billing-finance-core/src/modules/crm/deal.entity.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { DealStage } from '../../core/enums'; - -export class DealEntity { - id: string; - tenantId: string; - companyId?: string; - name: string; - stage: DealStage; - valueCents: number; - expectedAt?: Date; - createdAt: Date; - updatedAt: Date; -} diff --git a/billing-finance-core/src/modules/fiscal/README.md b/billing-finance-core/src/modules/fiscal/README.md deleted file mode 100644 index 7c686ee..0000000 --- a/billing-finance-core/src/modules/fiscal/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Fiscal Module - -Este módulo é responsável por todas as operações fiscais e contábeis do sistema, incluindo a emissão de Notas Fiscais (NFS-e), armazenamento de XML e PDF, e consulta de status. - -## Integração Nuvem Fiscal - -Utilizamos a API da [Nuvem Fiscal](https://nuvemfiscal.com.br) para a emissão de notas fiscais de serviço (NFS-e). - -### Configuração - -Para que a integração funcione, é necessário configurar as seguintes variáveis de ambiente no arquivo `.env`: - -```env -NUVEM_FISCAL_CLIENT_ID=seu_client_id -NUVEM_FISCAL_CLIENT_SECRET=seu_client_secret -``` - -### Arquitetura - -A integração é feita através do `NuvemFiscalProvider` (`src/modules/fiscal/providers/nuvem-fiscal.provider.ts`), que encapsula a lógica de autenticação (OAuth2) e comunicação com a API. - -O `FiscalService` utiliza este provider para realizar as operações. - -### Uso - -Para emitir uma nota fiscal de serviço: - -#### Via Código (Service) - -```typescript -import { FiscalService } from './fiscal.service'; - -constructor(private fiscalService: FiscalService) {} - -async emitir() { - const payload = { - // Dados da NFS-e conforme documentação da Nuvem Fiscal - }; - await this.fiscalService.emitirNotaServico(payload); -} -``` - -#### Via API (HTTP) - -Você pode testar a emissão fazendo uma requisição POST: - -``` -POST /fiscal/nfe -Content-Type: application/json - -{ - "referencia": "REF123", - "prestador": { ... }, - "tomador": { ... }, - "servicos": [ ... ] -} -``` - - -### Links Úteis - -- [Documentação API Nuvem Fiscal](https://dev.nuvemfiscal.com.br/docs/api/) -- [Painel Nuvem Fiscal](https://app.nuvemfiscal.com.br) diff --git a/billing-finance-core/src/modules/fiscal/fiscal.controller.ts b/billing-finance-core/src/modules/fiscal/fiscal.controller.ts deleted file mode 100644 index f04f8f4..0000000 --- a/billing-finance-core/src/modules/fiscal/fiscal.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Body, Controller, Post } from '@nestjs/common'; -import { FiscalService } from './fiscal.service'; - -@Controller('fiscal') -export class FiscalController { - constructor(private readonly fiscalService: FiscalService) { } - - @Post('nfe') - async emitirNfe(@Body() payload: any) { - return this.fiscalService.emitirNotaServico(payload); - } -} diff --git a/billing-finance-core/src/modules/fiscal/fiscal.entity.ts b/billing-finance-core/src/modules/fiscal/fiscal.entity.ts deleted file mode 100644 index 4056344..0000000 --- a/billing-finance-core/src/modules/fiscal/fiscal.entity.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { FiscalStatus } from '../../core/enums'; - -export class FiscalEntity { - id: string; - tenantId: string; - invoiceId?: string; - number?: string; - status: FiscalStatus; - pdfUrl?: string; - xmlUrl?: string; - createdAt: Date; - updatedAt: Date; -} diff --git a/billing-finance-core/src/modules/fiscal/fiscal.module.ts b/billing-finance-core/src/modules/fiscal/fiscal.module.ts deleted file mode 100644 index fac0625..0000000 --- a/billing-finance-core/src/modules/fiscal/fiscal.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Module } from '@nestjs/common'; -import { HttpModule } from '@nestjs/axios'; -import { FiscalService } from './fiscal.service'; -import { PrismaService } from '../../lib/postgres'; -import { NuvemFiscalProvider } from './providers/nuvem-fiscal.provider'; -import { FiscalController } from './fiscal.controller'; - -@Module({ - imports: [HttpModule], - controllers: [FiscalController], - providers: [FiscalService, PrismaService, NuvemFiscalProvider], - exports: [FiscalService], -}) -export class FiscalModule { } diff --git a/billing-finance-core/src/modules/fiscal/fiscal.service.ts b/billing-finance-core/src/modules/fiscal/fiscal.service.ts deleted file mode 100644 index 02fe3c4..0000000 --- a/billing-finance-core/src/modules/fiscal/fiscal.service.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { FiscalStatus } from '../../core/enums'; -import { PrismaService } from '../../lib/postgres'; - -interface CreateFiscalInput { - tenantId: string; - invoiceId?: string; - number?: string; - status?: FiscalStatus; - pdfUrl?: string; - xmlUrl?: string; -} - -import { NuvemFiscalProvider } from './providers/nuvem-fiscal.provider'; - -@Injectable() -export class FiscalService { - constructor( - private readonly prisma: PrismaService, - private readonly nuvemFiscalProvider: NuvemFiscalProvider, - ) { } - - create(data: CreateFiscalInput) { - return this.prisma.fiscalDocument.create({ - data: { - tenantId: data.tenantId, - invoiceId: data.invoiceId, - number: data.number, - status: data.status ?? FiscalStatus.DRAFT, - pdfUrl: data.pdfUrl, - xmlUrl: data.xmlUrl, - }, - }); - } - - list(tenantId: string) { - return this.prisma.fiscalDocument.findMany({ - where: { tenantId }, - orderBy: { createdAt: 'desc' }, - }); - } - - async emitirNotaServico(payload: any) { - return this.nuvemFiscalProvider.emitirNfe(payload); - } -} diff --git a/billing-finance-core/src/modules/fiscal/providers/nuvem-fiscal.provider.ts b/billing-finance-core/src/modules/fiscal/providers/nuvem-fiscal.provider.ts deleted file mode 100644 index 5d6795a..0000000 --- a/billing-finance-core/src/modules/fiscal/providers/nuvem-fiscal.provider.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { HttpService } from '@nestjs/axios'; -import { firstValueFrom } from 'rxjs'; - -@Injectable() -export class NuvemFiscalProvider { - private readonly logger = new Logger(NuvemFiscalProvider.name); - private readonly baseUrl = 'https://api.nuvemfiscal.com.br'; - private readonly authUrl = 'https://auth.nuvemfiscal.com.br/oauth/token'; - private accessToken: string | null = null; - private tokenExpiresAt: number = 0; - - constructor( - private readonly httpService: HttpService, - // Injecting ConfigService usually requires ConfigModule, assuming it's available globally or I'll use process.env as failover - ) { } - - private async authenticate(): Promise { - // Check if token is valid (with 60s buffer) - if (this.accessToken && Date.now() < this.tokenExpiresAt) { - return this.accessToken; - } - - const clientId = process.env.NUVEM_FISCAL_CLIENT_ID; - const clientSecret = process.env.NUVEM_FISCAL_CLIENT_SECRET; - - if (!clientId || !clientSecret) { - throw new Error('Nuvem Fiscal credentials not configured'); - } - - const params = new URLSearchParams(); - params.append('grant_type', 'client_credentials'); - params.append('client_id', clientId); - params.append('client_secret', clientSecret); - params.append('scope', 'nfe'); // Basic scope for NF-e - - try { - this.logger.log('Authenticating with Nuvem Fiscal...'); - const { data } = await firstValueFrom( - this.httpService.post(this.authUrl, params, { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - }), - ); - - this.accessToken = data.access_token; - // expires_in is in seconds - this.tokenExpiresAt = Date.now() + (data.expires_in * 1000) - 60000; - this.logger.log('Authenticated successfully'); - - return this.accessToken; - } catch (error) { - this.logger.error('Error authenticating with Nuvem Fiscal', error.response?.data || error.message); - throw error; - } - } - - async emitirNfe(payload: any): Promise { - const token = await this.authenticate(); - const url = `${this.baseUrl}/nfe/sefaz/requisicao`; // Endpoint for generating NFe from JSON - // Note: Endpoint might vary depending on exact Nuvem Fiscal API version. - // Common endpoint: POST /nfe/emitir or POST /nfe/sefaz/requisicao - // Based on Swagger, let's verify if possible. For now, using standard assumption. - - // Actually, let's check the swagger if I can. - // I read chunk 0 of swagger earlier. - // It mentioned "Envio de XML e PDF de NF-e". - - try { - const { data } = await firstValueFrom( - this.httpService.post(url, payload, { - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - }), - ); - return data; - } catch (error) { - this.logger.error('Error emitting NFe', error.response?.data || error.message); - throw error; - } - } - - async consultarNfe(id: string): Promise { - const token = await this.authenticate(); - const url = `${this.baseUrl}/nfe/${id}`; - - try { - const { data } = await firstValueFrom( - this.httpService.get(url, { - headers: { Authorization: `Bearer ${token}` } - }) - ); - return data; - } catch (error) { - this.logger.error('Error fetching NFe', error.response?.data || error.message); - throw error; - } - } -} diff --git a/billing-finance-core/src/modules/invoices/README.md b/billing-finance-core/src/modules/invoices/README.md deleted file mode 100644 index 71bba1f..0000000 --- a/billing-finance-core/src/modules/invoices/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Invoice Module - -Módulo responsável pela gestão de cobranças e faturas. É o coração financeiro do sistema. - -## Responsabilidades -- Gerar faturas (Invoices) avulsas ou recorrentes. -- Controlar status de pagamento (`PENDING`, `PAID`, `OVERDUE`, `CANCELED`). -- Calcular juros e multas (futuro). -- Vincular pagamentos às faturas. - -## Integração -- **Assinaturas**: Invoices são geradas automaticamente pelo módulo `subscriptions`. -- **Fiscal**: Quando uma invoice é paga, pode disparar a emissão de nota fiscal no módulo `fiscal`. - -## Multi-tenancy -- Isolamento total via `tenantId`. diff --git a/billing-finance-core/src/modules/invoices/invoice.controller.ts b/billing-finance-core/src/modules/invoices/invoice.controller.ts deleted file mode 100644 index a36711d..0000000 --- a/billing-finance-core/src/modules/invoices/invoice.controller.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Body, Controller, Get, Post, Req } from '@nestjs/common'; -import { IsDateString, IsEnum, IsInt, IsOptional, IsString, Min } from 'class-validator'; -import { Request } from 'express'; -import { InvoiceStatus } from '../../core/enums'; -import { getTenantContext } from '../../core/tenant.context'; -import { InvoiceService } from './invoice.service'; - -class CreateInvoiceDto { - @IsOptional() - @IsString() - subscriptionId?: string; - - @IsInt() - @Min(1) - amountCents: number; - - @IsDateString() - dueDate: string; - - @IsOptional() - @IsEnum(InvoiceStatus) - status?: InvoiceStatus; -} - -@Controller('invoices') -export class InvoiceController { - constructor(private readonly invoiceService: InvoiceService) {} - - @Post() - create(@Req() req: Request, @Body() body: CreateInvoiceDto) { - const { tenantId } = getTenantContext(req); - return this.invoiceService.create({ - tenantId, - subscriptionId: body.subscriptionId, - amountCents: body.amountCents, - dueDate: new Date(body.dueDate), - status: body.status, - }); - } - - @Get() - list(@Req() req: Request) { - const { tenantId } = getTenantContext(req); - return this.invoiceService.list(tenantId); - } -} diff --git a/billing-finance-core/src/modules/invoices/invoice.entity.ts b/billing-finance-core/src/modules/invoices/invoice.entity.ts deleted file mode 100644 index 708e0fa..0000000 --- a/billing-finance-core/src/modules/invoices/invoice.entity.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { InvoiceStatus } from '../../core/enums'; - -export class InvoiceEntity { - id: string; - tenantId: string; - subscriptionId?: string; - amountCents: number; - dueDate: Date; - status: InvoiceStatus; - paidAt?: Date; - createdAt: Date; - updatedAt: Date; -} diff --git a/billing-finance-core/src/modules/invoices/invoice.module.ts b/billing-finance-core/src/modules/invoices/invoice.module.ts deleted file mode 100644 index 7095377..0000000 --- a/billing-finance-core/src/modules/invoices/invoice.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { InvoiceController } from './invoice.controller'; -import { InvoiceService } from './invoice.service'; -import { PrismaService } from '../../lib/postgres'; - -@Module({ - controllers: [InvoiceController], - providers: [InvoiceService, PrismaService], - exports: [InvoiceService], -}) -export class InvoiceModule {} diff --git a/billing-finance-core/src/modules/invoices/invoice.service.ts b/billing-finance-core/src/modules/invoices/invoice.service.ts deleted file mode 100644 index 306ca81..0000000 --- a/billing-finance-core/src/modules/invoices/invoice.service.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InvoiceStatus } from '../../core/enums'; -import { PrismaService } from '../../lib/postgres'; - -interface CreateInvoiceInput { - tenantId: string; - subscriptionId?: string; - amountCents: number; - dueDate: Date; - status?: InvoiceStatus; -} - -@Injectable() -export class InvoiceService { - constructor(private readonly prisma: PrismaService) {} - - create(data: CreateInvoiceInput) { - return this.prisma.invoice.create({ - data: { - tenantId: data.tenantId, - subscriptionId: data.subscriptionId, - amountCents: data.amountCents, - dueDate: data.dueDate, - status: data.status ?? InvoiceStatus.PENDING, - }, - }); - } - - list(tenantId: string) { - return this.prisma.invoice.findMany({ - where: { tenantId }, - include: { payments: true }, - orderBy: { createdAt: 'desc' }, - }); - } -} diff --git a/billing-finance-core/src/modules/payments/README.md b/billing-finance-core/src/modules/payments/README.md deleted file mode 100644 index cb10fa9..0000000 --- a/billing-finance-core/src/modules/payments/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Payment Module - -Gerencia as transações financeiras e gateways de pagamento. - -## Responsabilidades -- Registrar tentativas de pagamento. -- Integrar com gateways (ex: Stripe, Pagar.me). -- Atualizar status da Invoice quando o pagamento é confirmado. - -## Multi-tenancy -- Pagamentos pertencem a uma Invoice, que pertence a um Tenant. Isolamento garantido. diff --git a/billing-finance-core/src/modules/payments/gateways/boleto.gateway.ts b/billing-finance-core/src/modules/payments/gateways/boleto.gateway.ts deleted file mode 100644 index d721b09..0000000 --- a/billing-finance-core/src/modules/payments/gateways/boleto.gateway.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { randomUUID } from 'crypto'; -import { Invoice, Payment } from '@prisma/client'; -import { PaymentStatus } from '../../../core/enums'; -import { PaymentGateway, PaymentGatewayResult, PaymentWebhookResult } from './gateway.interface'; - -export class BoletoGateway implements PaymentGateway { - name = 'boleto'; - - async createPayment(invoice: Invoice): Promise { - const externalId = `boleto_${randomUUID()}`; - return { - externalId, - status: PaymentStatus.PENDING, - metadata: { - barcode: `34191.79001 ${externalId.slice(0, 8)}`, - }, - }; - } - - async handleWebhook(payload: any): Promise { - return { - externalId: payload?.externalId ?? '', - status: payload?.status ?? PaymentStatus.CONFIRMED, - }; - } - - async reconcile(payment: Payment): Promise { - return payment.status as PaymentStatus; - } -} diff --git a/billing-finance-core/src/modules/payments/gateways/card.gateway.ts b/billing-finance-core/src/modules/payments/gateways/card.gateway.ts deleted file mode 100644 index dcc4804..0000000 --- a/billing-finance-core/src/modules/payments/gateways/card.gateway.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { randomUUID } from 'crypto'; -import { Invoice, Payment } from '@prisma/client'; -import { PaymentStatus } from '../../../core/enums'; -import { env } from '../../../lib/env'; -import { appLogger } from '../../../lib/logger'; -import { PaymentGateway, PaymentGatewayResult, PaymentWebhookResult } from './gateway.interface'; - -export class CardGateway implements PaymentGateway { - name = 'card'; - - async createPayment(invoice: Invoice): Promise { - if (!env.stripeApiKey) { - const externalId = `card_${randomUUID()}`; - return { - externalId, - status: PaymentStatus.PENDING, - metadata: { provider: 'mock' }, - }; - } - - const response = await fetch('https://api.stripe.com/v1/payment_intents', { - method: 'POST', - headers: { - Authorization: `Bearer ${env.stripeApiKey}`, - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: new URLSearchParams({ - amount: invoice.amountCents.toString(), - currency: 'brl', - 'metadata[invoiceId]': invoice.id, - }), - }); - - if (!response.ok) { - const errorText = await response.text(); - appLogger.error(`Stripe error: ${errorText}`); - return { - status: PaymentStatus.FAILED, - }; - } - - const payload = (await response.json()) as { id: string }; - return { - externalId: payload.id, - status: PaymentStatus.PENDING, - }; - } - - async handleWebhook(payload: any): Promise { - return { - externalId: payload?.data?.object?.id ?? payload?.externalId ?? '', - status: payload?.status ?? PaymentStatus.CONFIRMED, - }; - } - - async reconcile(payment: Payment): Promise { - return payment.status as PaymentStatus; - } -} diff --git a/billing-finance-core/src/modules/payments/gateways/gateway.interface.ts b/billing-finance-core/src/modules/payments/gateways/gateway.interface.ts deleted file mode 100644 index 193a61e..0000000 --- a/billing-finance-core/src/modules/payments/gateways/gateway.interface.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Invoice, Payment } from '@prisma/client'; -import { PaymentStatus } from '../../../core/enums'; - -export interface PaymentGatewayResult { - externalId?: string; - status: PaymentStatus; - redirectUrl?: string; - qrCode?: string; - metadata?: Record; -} - -export interface PaymentWebhookResult { - externalId: string; - status: PaymentStatus; -} - -export interface PaymentGateway { - name: string; - createPayment(invoice: Invoice): Promise; - handleWebhook(payload: unknown, signature?: string): Promise; - reconcile(payment: Payment): Promise; -} diff --git a/billing-finance-core/src/modules/payments/gateways/pix.gateway.ts b/billing-finance-core/src/modules/payments/gateways/pix.gateway.ts deleted file mode 100644 index 1edb71c..0000000 --- a/billing-finance-core/src/modules/payments/gateways/pix.gateway.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { randomUUID } from 'crypto'; -import { Invoice, Payment } from '@prisma/client'; -import { PaymentStatus } from '../../../core/enums'; -import { PaymentGateway, PaymentGatewayResult, PaymentWebhookResult } from './gateway.interface'; - -export class PixGateway implements PaymentGateway { - name = 'pix'; - - async createPayment(invoice: Invoice): Promise { - const externalId = `pix_${randomUUID()}`; - return { - externalId, - status: PaymentStatus.PENDING, - qrCode: `PIX-QRCODE-${externalId}`, - }; - } - - async handleWebhook(payload: any): Promise { - return { - externalId: payload?.externalId ?? '', - status: payload?.status ?? PaymentStatus.CONFIRMED, - }; - } - - async reconcile(payment: Payment): Promise { - return payment.status as PaymentStatus; - } -} diff --git a/billing-finance-core/src/modules/payments/payment.controller.ts b/billing-finance-core/src/modules/payments/payment.controller.ts deleted file mode 100644 index 19fdd04..0000000 --- a/billing-finance-core/src/modules/payments/payment.controller.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Body, Controller, Param, Post, Req } from '@nestjs/common'; -import { IsEnum, IsString } from 'class-validator'; -import { Request } from 'express'; -import { getTenantContext } from '../../core/tenant.context'; -import { PaymentService } from './payment.service'; - -enum PaymentMethod { - PIX = 'pix', - BOLETO = 'boleto', - CARD = 'card', -} - -class CreatePaymentDto { - @IsEnum(PaymentMethod) - method: PaymentMethod; - - @IsString() - gateway: string; -} - -@Controller('payments') -export class PaymentController { - constructor(private readonly paymentService: PaymentService) {} - - @Post(':invoiceId') - create( - @Req() req: Request, - @Param('invoiceId') invoiceId: string, - @Body() body: CreatePaymentDto, - ) { - const { tenantId } = getTenantContext(req); - return this.paymentService.createPayment({ - tenantId, - invoiceId, - method: body.method, - gateway: body.gateway, - }); - } -} diff --git a/billing-finance-core/src/modules/payments/payment.entity.ts b/billing-finance-core/src/modules/payments/payment.entity.ts deleted file mode 100644 index 126d6d3..0000000 --- a/billing-finance-core/src/modules/payments/payment.entity.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { PaymentStatus } from '../../core/enums'; - -export class PaymentEntity { - id: string; - tenantId: string; - invoiceId: string; - gateway: string; - method: string; - externalId?: string; - status: PaymentStatus; - amountCents: number; - createdAt: Date; - updatedAt: Date; -} diff --git a/billing-finance-core/src/modules/payments/payment.module.ts b/billing-finance-core/src/modules/payments/payment.module.ts deleted file mode 100644 index 5ee4890..0000000 --- a/billing-finance-core/src/modules/payments/payment.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { PaymentController } from './payment.controller'; -import { PaymentService } from './payment.service'; -import { PrismaService } from '../../lib/postgres'; - -@Module({ - controllers: [PaymentController], - providers: [PaymentService, PrismaService], - exports: [PaymentService], -}) -export class PaymentModule {} diff --git a/billing-finance-core/src/modules/payments/payment.service.ts b/billing-finance-core/src/modules/payments/payment.service.ts deleted file mode 100644 index 5853b5c..0000000 --- a/billing-finance-core/src/modules/payments/payment.service.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { PrismaService } from '../../lib/postgres'; -import { PaymentStatus } from '../../core/enums'; -import { PaymentGateway } from './gateways/gateway.interface'; -import { PixGateway } from './gateways/pix.gateway'; -import { BoletoGateway } from './gateways/boleto.gateway'; -import { CardGateway } from './gateways/card.gateway'; - -interface CreatePaymentInput { - tenantId: string; - invoiceId: string; - method: string; - gateway: string; -} - -@Injectable() -export class PaymentService { - private readonly gateways: Record; - - constructor(private readonly prisma: PrismaService) { - const available = [new PixGateway(), new BoletoGateway(), new CardGateway()]; - this.gateways = available.reduce((acc, gateway) => { - acc[gateway.name] = gateway; - return acc; - }, {} as Record); - } - - private getGateway(name: string): PaymentGateway { - const gateway = this.gateways[name]; - if (!gateway) { - throw new NotFoundException(`Gateway ${name} not supported`); - } - return gateway; - } - - async createPayment(data: CreatePaymentInput) { - const invoice = await this.prisma.invoice.findFirst({ - where: { id: data.invoiceId, tenantId: data.tenantId }, - }); - - if (!invoice) { - throw new NotFoundException('Invoice not found'); - } - - const gateway = this.getGateway(data.gateway); - const gatewayResult = await gateway.createPayment(invoice); - - return this.prisma.payment.create({ - data: { - tenantId: data.tenantId, - invoiceId: invoice.id, - gateway: gateway.name, - method: data.method, - externalId: gatewayResult.externalId, - status: gatewayResult.status ?? PaymentStatus.PENDING, - amountCents: invoice.amountCents, - }, - }); - } - - async reconcileByWebhook(gatewayName: string, payload: unknown) { - const gateway = this.getGateway(gatewayName); - const webhook = await gateway.handleWebhook(payload); - - const existing = await this.prisma.payment.findFirst({ - where: { gateway: gatewayName, externalId: webhook.externalId }, - }); - - if (!existing) { - return { ignored: true, reason: 'payment-not-found' }; - } - - if (existing.status === webhook.status) { - return existing; - } - - return this.prisma.payment.update({ - where: { id: existing.id }, - data: { status: webhook.status }, - }); - } -} diff --git a/billing-finance-core/src/modules/plans/README.md b/billing-finance-core/src/modules/plans/README.md deleted file mode 100644 index a26d710..0000000 --- a/billing-finance-core/src/modules/plans/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Plan Module - -Gerencia os planos de assinatura disponíveis na plataforma (SaaS). - -## Responsabilidades -- Criar e listar planos. -- Definir preços (em centavos). -- Definir ciclo de cobrança (`MONTHLY`, `YEARLY`). -- Definir limites (soft limits e hard limits). - -## Uso -Planos são geralmente globais ou definidos pelo Admin da plataforma, mas neste contexto multitenant, cada Tenant pode ter seus próprios planos se o sistema for um "SaaS Builder", ou os planos podem ser do sistema pai. -*Nota: Verifique a regra de negócio se Planos são compartilhados ou por Tenant via `tenantId`.* (No schema atual, Plan não tem `tenantId`, o que implica que são globais do sistema, ou faltou o campo). diff --git a/billing-finance-core/src/modules/plans/plan.controller.ts b/billing-finance-core/src/modules/plans/plan.controller.ts deleted file mode 100644 index 97273e6..0000000 --- a/billing-finance-core/src/modules/plans/plan.controller.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Body, Controller, Get, Post } from '@nestjs/common'; -import { IsEnum, IsInt, IsOptional, IsString, Min } from 'class-validator'; -import { BillingCycle } from '../../core/enums'; -import { PlanService } from './plan.service'; - -class CreatePlanDto { - @IsString() - name: string; - - @IsInt() - @Min(0) - priceCents: number; - - @IsEnum(BillingCycle) - billingCycle: BillingCycle; - - @IsOptional() - @IsInt() - softLimit?: number; - - @IsOptional() - @IsInt() - hardLimit?: number; -} - -@Controller('plans') -export class PlanController { - constructor(private readonly planService: PlanService) {} - - @Post() - create(@Body() body: CreatePlanDto) { - return this.planService.create(body); - } - - @Get() - list() { - return this.planService.list(); - } -} diff --git a/billing-finance-core/src/modules/plans/plan.entity.ts b/billing-finance-core/src/modules/plans/plan.entity.ts deleted file mode 100644 index 82fdcea..0000000 --- a/billing-finance-core/src/modules/plans/plan.entity.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { BillingCycle } from '../../core/enums'; - -export class PlanEntity { - id: string; - name: string; - priceCents: number; - billingCycle: BillingCycle; - softLimit?: number; - hardLimit?: number; - createdAt: Date; - updatedAt: Date; -} diff --git a/billing-finance-core/src/modules/plans/plan.module.ts b/billing-finance-core/src/modules/plans/plan.module.ts deleted file mode 100644 index 5dfb920..0000000 --- a/billing-finance-core/src/modules/plans/plan.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { PlanController } from './plan.controller'; -import { PlanService } from './plan.service'; -import { PrismaService } from '../../lib/postgres'; - -@Module({ - controllers: [PlanController], - providers: [PlanService, PrismaService], - exports: [PlanService], -}) -export class PlanModule {} diff --git a/billing-finance-core/src/modules/plans/plan.service.ts b/billing-finance-core/src/modules/plans/plan.service.ts deleted file mode 100644 index 26b84c4..0000000 --- a/billing-finance-core/src/modules/plans/plan.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { BillingCycle } from '../../core/enums'; -import { PrismaService } from '../../lib/postgres'; - -interface CreatePlanInput { - name: string; - priceCents: number; - billingCycle: BillingCycle; - softLimit?: number; - hardLimit?: number; -} - -@Injectable() -export class PlanService { - constructor(private readonly prisma: PrismaService) {} - - create(data: CreatePlanInput) { - return this.prisma.plan.create({ data }); - } - - list() { - return this.prisma.plan.findMany({ orderBy: { createdAt: 'desc' } }); - } -} diff --git a/billing-finance-core/src/modules/subscriptions/README.md b/billing-finance-core/src/modules/subscriptions/README.md deleted file mode 100644 index b44d6c1..0000000 --- a/billing-finance-core/src/modules/subscriptions/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Subscription Module - -Gerencia as recorrências (assinaturas) dos clientes (Tenants). - -## Responsabilidades -- Criar assinatura vinculando um Tenant a um Plano. -- Controlar status (`ACTIVE`, `PAST_DUE`, `CANCELED`). -- Gerar Invoices automaticamente baseadas no ciclo de cobrança. - -## Multi-tenancy -- Isolado por `tenantId`. diff --git a/billing-finance-core/src/modules/subscriptions/subscription.controller.ts b/billing-finance-core/src/modules/subscriptions/subscription.controller.ts deleted file mode 100644 index 944ca61..0000000 --- a/billing-finance-core/src/modules/subscriptions/subscription.controller.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Body, Controller, Get, Post, Req } from '@nestjs/common'; -import { IsDateString, IsEnum, IsOptional, IsString } from 'class-validator'; -import { Request } from 'express'; -import { SubscriptionStatus } from '../../core/enums'; -import { getTenantContext } from '../../core/tenant.context'; -import { SubscriptionService } from './subscription.service'; - -class CreateSubscriptionDto { - @IsString() - planId: string; - - @IsDateString() - startDate: string; - - @IsDateString() - nextDueDate: string; - - @IsOptional() - @IsEnum(SubscriptionStatus) - status?: SubscriptionStatus; -} - -@Controller('subscriptions') -export class SubscriptionController { - constructor(private readonly subscriptionService: SubscriptionService) {} - - @Post() - create(@Req() req: Request, @Body() body: CreateSubscriptionDto) { - const { tenantId } = getTenantContext(req); - return this.subscriptionService.create({ - tenantId, - planId: body.planId, - startDate: new Date(body.startDate), - nextDueDate: new Date(body.nextDueDate), - status: body.status, - }); - } - - @Get() - list(@Req() req: Request) { - const { tenantId } = getTenantContext(req); - return this.subscriptionService.list(tenantId); - } -} diff --git a/billing-finance-core/src/modules/subscriptions/subscription.entity.ts b/billing-finance-core/src/modules/subscriptions/subscription.entity.ts deleted file mode 100644 index 0dc88a1..0000000 --- a/billing-finance-core/src/modules/subscriptions/subscription.entity.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { SubscriptionStatus } from '../../core/enums'; - -export class SubscriptionEntity { - id: string; - tenantId: string; - planId: string; - status: SubscriptionStatus; - startDate: Date; - nextDueDate: Date; - createdAt: Date; - updatedAt: Date; -} diff --git a/billing-finance-core/src/modules/subscriptions/subscription.module.ts b/billing-finance-core/src/modules/subscriptions/subscription.module.ts deleted file mode 100644 index 9a6d0eb..0000000 --- a/billing-finance-core/src/modules/subscriptions/subscription.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { SubscriptionController } from './subscription.controller'; -import { SubscriptionService } from './subscription.service'; -import { PrismaService } from '../../lib/postgres'; - -@Module({ - controllers: [SubscriptionController], - providers: [SubscriptionService, PrismaService], - exports: [SubscriptionService], -}) -export class SubscriptionModule {} diff --git a/billing-finance-core/src/modules/subscriptions/subscription.service.ts b/billing-finance-core/src/modules/subscriptions/subscription.service.ts deleted file mode 100644 index 4be69f0..0000000 --- a/billing-finance-core/src/modules/subscriptions/subscription.service.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { SubscriptionStatus } from '../../core/enums'; -import { PrismaService } from '../../lib/postgres'; - -interface CreateSubscriptionInput { - tenantId: string; - planId: string; - startDate: Date; - nextDueDate: Date; - status?: SubscriptionStatus; -} - -@Injectable() -export class SubscriptionService { - constructor(private readonly prisma: PrismaService) {} - - create(data: CreateSubscriptionInput) { - return this.prisma.subscription.create({ - data: { - tenantId: data.tenantId, - planId: data.planId, - startDate: data.startDate, - nextDueDate: data.nextDueDate, - status: data.status ?? SubscriptionStatus.ACTIVE, - }, - }); - } - - list(tenantId: string) { - return this.prisma.subscription.findMany({ - where: { tenantId }, - include: { plan: true }, - orderBy: { createdAt: 'desc' }, - }); - } -} diff --git a/billing-finance-core/src/modules/tenants/README.md b/billing-finance-core/src/modules/tenants/README.md deleted file mode 100644 index 54eea06..0000000 --- a/billing-finance-core/src/modules/tenants/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Tenant Module - -Gerencia os clientes da plataforma (empresas que assinam o SaaS). - -## Responsabilidades -- Cadastro de novos Tenants (Onboarding). -- Gestão de dados cadastrais (CNPJ/TaxId, Nome). -- Status da conta (`ACTIVE`, `INACTIVE`). - -## Importante -O `tenantId` gerado aqui é usado em **todas** as outras tabelas do sistema para garantir o isolamento dos dados. diff --git a/billing-finance-core/src/modules/tenants/tenant.controller.ts b/billing-finance-core/src/modules/tenants/tenant.controller.ts deleted file mode 100644 index 9474ecd..0000000 --- a/billing-finance-core/src/modules/tenants/tenant.controller.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Body, Controller, Get, Post } from '@nestjs/common'; -import { IsOptional, IsString } from 'class-validator'; -import { TenantService } from './tenant.service'; - -class CreateTenantDto { - @IsString() - name: string; - - @IsOptional() - @IsString() - taxId?: string; - - @IsOptional() - @IsString() - status?: string; -} - -@Controller('tenants') -export class TenantController { - constructor(private readonly tenantService: TenantService) {} - - @Post() - create(@Body() body: CreateTenantDto) { - return this.tenantService.create(body); - } - - @Get() - list() { - return this.tenantService.list(); - } -} diff --git a/billing-finance-core/src/modules/tenants/tenant.entity.ts b/billing-finance-core/src/modules/tenants/tenant.entity.ts deleted file mode 100644 index 1ee4bb5..0000000 --- a/billing-finance-core/src/modules/tenants/tenant.entity.ts +++ /dev/null @@ -1,8 +0,0 @@ -export class TenantEntity { - id: string; - name: string; - taxId?: string; - status: string; - createdAt: Date; - updatedAt: Date; -} diff --git a/billing-finance-core/src/modules/tenants/tenant.module.ts b/billing-finance-core/src/modules/tenants/tenant.module.ts deleted file mode 100644 index eb1e370..0000000 --- a/billing-finance-core/src/modules/tenants/tenant.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TenantController } from './tenant.controller'; -import { TenantService } from './tenant.service'; -import { PrismaService } from '../../lib/postgres'; - -@Module({ - controllers: [TenantController], - providers: [TenantService, PrismaService], - exports: [TenantService], -}) -export class TenantModule {} diff --git a/billing-finance-core/src/modules/tenants/tenant.service.ts b/billing-finance-core/src/modules/tenants/tenant.service.ts deleted file mode 100644 index 3f94802..0000000 --- a/billing-finance-core/src/modules/tenants/tenant.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '../../lib/postgres'; - -interface CreateTenantInput { - name: string; - taxId?: string; - status?: string; -} - -@Injectable() -export class TenantService { - constructor(private readonly prisma: PrismaService) {} - - create(data: CreateTenantInput) { - return this.prisma.tenant.create({ - data: { - name: data.name, - taxId: data.taxId, - status: data.status ?? 'ACTIVE', - }, - }); - } - - list() { - return this.prisma.tenant.findMany({ - orderBy: { createdAt: 'desc' }, - }); - } -} diff --git a/billing-finance-core/src/modules/webhooks/README.md b/billing-finance-core/src/modules/webhooks/README.md deleted file mode 100644 index 0bc01f0..0000000 --- a/billing-finance-core/src/modules/webhooks/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Webhook Module - -Recebe notificações assíncronas de gateways de pagamento e outros serviços externos. - -## Responsabilidades -- Receber POSTs de serviços externos (Stripe). -- Validar assinaturas de segurança para garantir origem. -- Processar eventos (ex: `payment_intent.succeeded`) e encaminhar para os módulos responsáveis (ex: `payments`, `subscriptions`). - -## Segurança -- Este módulo possui rotas públicas, mas protegidas por validação de assinatura (`stripe-signature`, etc.) ou segredo no header, implementado no `AuthGuard` ou no próprio controller. diff --git a/billing-finance-core/src/modules/webhooks/webhook.controller.ts b/billing-finance-core/src/modules/webhooks/webhook.controller.ts deleted file mode 100644 index fc70198..0000000 --- a/billing-finance-core/src/modules/webhooks/webhook.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Body, Controller, Param, Post } from '@nestjs/common'; -import { WebhookService } from './webhook.service'; - -@Controller('webhooks') -export class WebhookController { - constructor(private readonly webhookService: WebhookService) {} - - @Post(':gateway') - handle(@Param('gateway') gateway: string, @Body() payload: unknown) { - return this.webhookService.handleGatewayWebhook(gateway, payload); - } -} diff --git a/billing-finance-core/src/modules/webhooks/webhook.module.ts b/billing-finance-core/src/modules/webhooks/webhook.module.ts deleted file mode 100644 index 4eec987..0000000 --- a/billing-finance-core/src/modules/webhooks/webhook.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { WebhookController } from './webhook.controller'; -import { WebhookService } from './webhook.service'; -import { PaymentService } from '../payments/payment.service'; -import { PrismaService } from '../../lib/postgres'; - -@Module({ - controllers: [WebhookController], - providers: [WebhookService, PaymentService, PrismaService], -}) -export class WebhookModule {} diff --git a/billing-finance-core/src/modules/webhooks/webhook.service.ts b/billing-finance-core/src/modules/webhooks/webhook.service.ts deleted file mode 100644 index be2a1c5..0000000 --- a/billing-finance-core/src/modules/webhooks/webhook.service.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { PaymentService } from '../payments/payment.service'; - -@Injectable() -export class WebhookService { - constructor(private readonly paymentService: PaymentService) {} - - handleGatewayWebhook(gateway: string, payload: unknown) { - return this.paymentService.reconcileByWebhook(gateway, payload); - } -} diff --git a/billing-finance-core/tsconfig.json b/billing-finance-core/tsconfig.json deleted file mode 100644 index cb9e902..0000000 --- a/billing-finance-core/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "declaration": true, - "removeComments": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, - "target": "ES2021", - "sourceMap": true, - "outDir": "./dist", - "baseUrl": "./", - "incremental": true, - "skipLibCheck": true - }, - "include": ["src/**/*.ts"] -} diff --git a/crm-core/.dockerignore b/crm-core/.dockerignore deleted file mode 100644 index b0ddfcf..0000000 --- a/crm-core/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -.git -.env -.gitignore -Dockerfile -README.md -CRM-CORE.md -docs -*.log diff --git a/crm-core/.env.example b/crm-core/.env.example deleted file mode 100644 index 23ac62c..0000000 --- a/crm-core/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -APP_ENV=development -HTTP_ADDR=:8080 -DATABASE_URL=postgres://crm:crm@localhost:5432/crm_core?sslmode=disable -JWKS_URL=http://identity-gateway/.well-known/jwks.json -REQUEST_TIMEOUT=10s -SHUTDOWN_TIMEOUT=10s diff --git a/crm-core/.gitignore b/crm-core/.gitignore deleted file mode 100644 index 8185a94..0000000 --- a/crm-core/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.env -*.log -.DS_Store -crm-core -main -coverage diff --git a/crm-core/CRM-CORE.md b/crm-core/CRM-CORE.md deleted file mode 100644 index a10bb7c..0000000 --- a/crm-core/CRM-CORE.md +++ /dev/null @@ -1,99 +0,0 @@ -# CRM-CORE - -O `crm-core` é um microserviço especializado na gestão operacional de relacionamento com o cliente (CRM) para plataformas B2B SaaS. Ele é agnóstico a regras de faturamento e foca exclusivamente no ciclo de vida de vendas e gestão de contatos. - -## 📋 Visão Geral - -Este serviço foi desenhado para ser integrado via API e não possui interface própria. Ele delega a autenticação para o `identity-gateway` e opera em estrito isolamento multi-tenant. - -**O que ele faz:** -- ✅ Gestão de Contas (Accounts) e Contatos. -- ✅ Pipelines de Vendas, Estágios e Oportunidades (Deals). -- ✅ Registro de Atividades e Notas. - -**O que ele NÃO faz:** -- ❌ Faturamento ou ERP (ver `billing-finance-core`). -- ❌ Deployment de infraestrutura (ver `automation-jobs-core`). -- ❌ Autenticação de usuários. - -### Arquitetura - -```mermaid -graph TD - Client[Frontend / API Gateway] -->|HTTP REST| API[CRM Core API] - API -->|Valida JWT| Identity[Identity Gateway] - - subgraph CRM Domain - API --> Accounts - API --> Contacts - API --> Deals - API --> Activities - end - - API --> DB[(PostgreSQL)] -``` - -## 🚀 Estrutura do Projeto - -O projeto é escrito em **Go** e segue a Clean Architecture simplificada: - -| Diretório | Descrição | -| :--- | :--- | -| `cmd/api` | Ponto de entrada da aplicação. | -| `internal/domain` | Entidades e interfaces. | -| `internal/usecase` | Regras de negócio. | -| `internal/handler` | Controladores HTTP (Chi Router). | -| `internal/repository` | Acesso a dados (pgx). | - -## 🛠️ Tecnologias e Otimizações - -- **Linguagem**: Go 1.23+ -- **Router**: Chi -- **Database Driver**: Jackc/pgx -- **Containerização**: - - Baseada em `gcr.io/distroless/static:nonroot`. - - Binário compilado estaticamente com `-ldflags="-w -s"`. - - Imagem final ultraleve (~20MB). - -## 💻 Como Executar - -### Docker (Recomendado) - -```bash -# Build -docker build -t crm-core . - -# Run -docker run -p 8080:8080 --env-file .env crm-core -``` -A API estará disponível na porta `8080`. - -### Desenvolvimento - -1. **Dependências**: Go 1.23+, PostgreSQL. -2. **Setup**: - ```bash - cp .env.example .env - go mod tidy - ``` -3. **Executar**: - ```bash - go run ./cmd/api/main.go - ``` - -## 🔐 Segurança e Multi-tenancy - -O serviço implementa **Row-Level Security (RLS)** lógico na aplicação: - -1. **Trust**: Confia em tokens JWT assinados pelo `identity-gateway`. -2. **Contexto**: O `tenantId` é extraído do token em cada requisição. -3. **Isolamento**: Todos os repositórios injetam automaticamente `WHERE tenant_id = $1` em todas as queries. - -## 🔧 Detalhes do Dockerfile - - -- **Builder**: Imagem `golang:1.23-alpine`. Compila o binário estático. -- **Runtime (Distroless)**: Imagem `static:nonroot` do Google. - - Sem shell (`/bin/sh`). - - Sem gerenciador de pacotes (`apk`). - - Executa como usuário não-privilegiado (UID 65532). diff --git a/crm-core/Dockerfile b/crm-core/Dockerfile deleted file mode 100644 index 2182de5..0000000 --- a/crm-core/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# Dockerfile -FROM docker.io/library/golang:1.23-alpine AS builder - -WORKDIR /app - -COPY go.mod go.sum ./ -RUN go mod download - -COPY . . - -# Build with optimization flags -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /app/crm-core ./cmd/api - -# Use Google Distroless static image for minimal size and security -FROM gcr.io/distroless/static:nonroot - -WORKDIR /app - -COPY --from=builder /app/crm-core . - -# Non-root user for security -USER nonroot:nonroot - -EXPOSE 8080 - -CMD ["./crm-core"] diff --git a/crm-core/Makefile b/crm-core/Makefile deleted file mode 100644 index c3427ab..0000000 --- a/crm-core/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -APP_NAME=crm-core -DB_URL?=postgres://crm:crm@localhost:5432/crm_core?sslmode=disable -MIGRATE?=migrate - -.PHONY: fmt lint test sqlc run migrate-up migrate-down - -fmt: - go fmt ./... - -lint: - golangci-lint run ./... - -test: - go test ./... - -sqlc: - sqlc generate -f internal/db/sqlc/sqlc.yaml - -migrate-up: - $(MIGRATE) -path internal/db/migrations -database "$(DB_URL)" up - -migrate-down: - $(MIGRATE) -path internal/db/migrations -database "$(DB_URL)" down 1 - -run: - go run ./cmd/api diff --git a/crm-core/cmd/api/main.go b/crm-core/cmd/api/main.go deleted file mode 100644 index 9d3bae1..0000000 --- a/crm-core/cmd/api/main.go +++ /dev/null @@ -1,101 +0,0 @@ -package main - -import ( - "context" - "log/slog" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "crm-core/internal/api/handlers" - "crm-core/internal/api/router" - "crm-core/internal/application/accounts" - "crm-core/internal/application/activities" - "crm-core/internal/application/contacts" - "crm-core/internal/application/deals" - "crm-core/internal/application/notes" - "crm-core/internal/application/stages" - "crm-core/internal/application/tags" - "crm-core/internal/config" - "crm-core/internal/infrastructure/auth" - "crm-core/internal/infrastructure/postgres" - "crm-core/internal/infrastructure/repositories" - "crm-core/internal/observability" -) - -func main() { - cfg := config.MustLoad() - logger := observability.NewLogger(cfg.Env) - - ctx := context.Background() - pool, err := postgres.NewPool(ctx, cfg.DatabaseURL) - if err != nil { - logger.Error("database connection failed", slog.Any("error", err)) - os.Exit(1) - } - defer pool.Close() - - jwtMiddleware, err := auth.NewMiddleware(cfg.JWKSURL) - if err != nil { - logger.Error("jwks init failed", slog.Any("error", err)) - os.Exit(1) - } - - accountsRepo := repositories.NewAccountsRepository(pool) - contactsRepo := repositories.NewContactsRepository(pool) - dealsRepo := repositories.NewDealsRepository(pool) - stagesRepo := repositories.NewStagesRepository(pool) - activitiesRepo := repositories.NewActivitiesRepository(pool) - notesRepo := repositories.NewNotesRepository(pool) - tagsRepo := repositories.NewTagsRepository(pool) - - accountsService := accounts.NewService(accountsRepo) - contactsService := contacts.NewService(contactsRepo) - dealsService := deals.NewService(dealsRepo) - stagesService := stages.NewService(stagesRepo) - activitiesService := activities.NewService(activitiesRepo) - notesService := notes.NewService(notesRepo) - tagsService := tags.NewService(tagsRepo) - - handlerSet := handlers.HandlerSet{ - Accounts: handlers.NewAccountsHandler(accountsService), - Contacts: handlers.NewContactsHandler(contactsService), - Deals: handlers.NewDealsHandler(dealsService), - Stages: handlers.NewStagesHandler(stagesService), - Activities: handlers.NewActivitiesHandler(activitiesService), - Notes: handlers.NewNotesHandler(notesService), - Tags: handlers.NewTagsHandler(tagsService), - } - - r := router.NewRouter(logger, jwtMiddleware, handlerSet) - - srv := &http.Server{ - Addr: cfg.HTTPAddr, - Handler: r, - ReadTimeout: cfg.RequestTimeout, - WriteTimeout: cfg.RequestTimeout, - IdleTimeout: 30 * time.Second, - } - - go func() { - logger.Info("api started", slog.String("addr", cfg.HTTPAddr)) - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - logger.Error("http server error", slog.Any("error", err)) - os.Exit(1) - } - }() - - quit := make(chan os.Signal, 1) - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - - ctxShutdown, cancel := context.WithTimeout(context.Background(), cfg.ShutdownTimeout) - defer cancel() - if err := srv.Shutdown(ctxShutdown); err != nil { - logger.Error("http shutdown error", slog.Any("error", err)) - os.Exit(1) - } - logger.Info("api stopped") -} diff --git a/crm-core/docs/api.md b/crm-core/docs/api.md deleted file mode 100644 index a7c1458..0000000 --- a/crm-core/docs/api.md +++ /dev/null @@ -1,50 +0,0 @@ -# API Contract - -Base path: `/api/v1` - -## Accounts -- `POST /accounts` -- `GET /accounts` -- `GET /accounts/{id}` -- `PATCH /accounts/{id}` -- `DELETE /accounts/{id}` (soft delete) - -## Contacts -- `POST /contacts` -- `GET /contacts` -- `GET /contacts/{id}` -- `PATCH /contacts/{id}` -- `DELETE /contacts/{id}` (soft delete) - -## Pipelines/Stages -- `POST /pipelines` -- `GET /pipelines` -- `POST /pipelines/{id}/stages` -- `GET /pipelines/{id}/stages` - -## Deals -- `POST /deals` -- `GET /deals?status=open|won|lost&stageId=&accountId=` -- `GET /deals/{id}` -- `PATCH /deals/{id}` -- `POST /deals/{id}/move-stage` -- `POST /deals/{id}/close` - -## Activities -- `POST /activities` -- `GET /activities?status=open|done&dealId=&assignedUserId=` -- `POST /activities/{id}/complete` -- `DELETE /activities/{id}` (cancel) - -## Notes -- `POST /notes` -- `GET /notes?entityType=&entityId=` - -## Tags -- `POST /tags` -- `GET /tags` -- `POST /tags/assign` -- `POST /tags/unassign` - -## Health -- `GET /health` diff --git a/crm-core/docs/architecture.md b/crm-core/docs/architecture.md deleted file mode 100644 index d975cb9..0000000 --- a/crm-core/docs/architecture.md +++ /dev/null @@ -1,10 +0,0 @@ -# crm-core Architecture - -`crm-core` follows a layered architecture: - -- **API**: HTTP handlers, routing, and middleware. -- **Application**: use cases and orchestration. -- **Domain**: core CRM entities and rules. -- **Infrastructure**: PostgreSQL repositories, JWT validation, logging. - -The service is multi-tenant by design and enforces `tenant_id` on all database access. diff --git a/crm-core/docs/domain-model.md b/crm-core/docs/domain-model.md deleted file mode 100644 index 553e31b..0000000 --- a/crm-core/docs/domain-model.md +++ /dev/null @@ -1,13 +0,0 @@ -# Domain Model - -Entities: - -- **Accounts**: companies/organizations. -- **Contacts**: people linked to accounts or standalone. -- **Pipelines & Stages**: sales flow definitions. -- **Deals**: opportunities tied to pipelines/stages. -- **Activities**: tasks/calls/meetings with assignments. -- **Notes**: free-form notes attached to entities. -- **Tags**: label entities with unique names per tenant. - -All entities include `tenant_id`, `created_at`, and `updated_at` for auditability. diff --git a/crm-core/docs/security.md b/crm-core/docs/security.md deleted file mode 100644 index 7d910b4..0000000 --- a/crm-core/docs/security.md +++ /dev/null @@ -1,7 +0,0 @@ -# Security - -- JWT required for all routes except `/health`. -- JWKS validation used for token verification. -- Required claims: `sub` (user ID), `tenantId`, `roles`. -- Authorization scopes: `crm.read`, `crm.write`, `crm.admin`. -- Tenant isolation enforced on every query via `tenant_id`. diff --git a/crm-core/go.mod b/crm-core/go.mod deleted file mode 100644 index cd88252..0000000 --- a/crm-core/go.mod +++ /dev/null @@ -1,20 +0,0 @@ -module crm-core - -go 1.22 - -require ( - github.com/MicahParks/keyfunc/v2 v2.1.0 - github.com/go-chi/chi/v5 v5.0.10 - github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.5.5 -) - -require ( - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/text v0.14.0 // indirect -) diff --git a/crm-core/go.sum b/crm-core/go.sum deleted file mode 100644 index 982e881..0000000 --- a/crm-core/go.sum +++ /dev/null @@ -1,36 +0,0 @@ -github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k= -github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= -github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= -github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= -github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/crm-core/internal/api/handlers/accounts_handler.go b/crm-core/internal/api/handlers/accounts_handler.go deleted file mode 100644 index 3986ab4..0000000 --- a/crm-core/internal/api/handlers/accounts_handler.go +++ /dev/null @@ -1,151 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - - httpapi "crm-core/internal/api/http" - "crm-core/internal/application/accounts" - domain "crm-core/internal/domain/accounts" - "crm-core/internal/infrastructure/auth" - - "github.com/go-chi/chi/v5" -) - -type AccountsHandler struct { - service *accounts.Service -} - -func NewAccountsHandler(service *accounts.Service) *AccountsHandler { - return &AccountsHandler{service: service} -} - -type accountCreateRequest struct { - Name string `json:"name"` - LegalName *string `json:"legal_name"` - Document *string `json:"document"` - Website *string `json:"website"` - Industry *string `json:"industry"` - Size *string `json:"size"` - ExternalRefs map[string]string `json:"external_refs"` -} - -func (h *AccountsHandler) Create(w http.ResponseWriter, r *http.Request) { - var req accountCreateRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - httpapi.RespondError(w, http.StatusBadRequest, "invalid payload") - return - } - if req.Name == "" { - httpapi.RespondError(w, http.StatusBadRequest, "name is required") - return - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - account, err := h.service.Create(r.Context(), accounts.CreateAccountInput{ - TenantID: tenantID, - Name: req.Name, - LegalName: req.LegalName, - Document: req.Document, - Website: req.Website, - Industry: req.Industry, - Size: req.Size, - ExternalRefs: req.ExternalRefs, - }) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to create account") - return - } - httpapi.RespondJSON(w, http.StatusCreated, account) -} - -func (h *AccountsHandler) List(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - accountsList, err := h.service.List(r.Context(), tenantID) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to list accounts") - return - } - httpapi.RespondJSON(w, http.StatusOK, accountsList) -} - -func (h *AccountsHandler) Get(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - id := chi.URLParam(r, "id") - account, err := h.service.Get(r.Context(), tenantID, id) - if err != nil { - httpapi.RespondError(w, http.StatusNotFound, "account not found") - return - } - httpapi.RespondJSON(w, http.StatusOK, account) -} - -type accountUpdateRequest struct { - Name *string `json:"name"` - LegalName *string `json:"legal_name"` - Document *string `json:"document"` - Website *string `json:"website"` - Industry *string `json:"industry"` - Size *string `json:"size"` - Status *string `json:"status"` -} - -func (h *AccountsHandler) Update(w http.ResponseWriter, r *http.Request) { - var req accountUpdateRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - httpapi.RespondError(w, http.StatusBadRequest, "invalid payload") - return - } - id := chi.URLParam(r, "id") - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - var statusPtr *domain.Status - if req.Status != nil { - status := domain.Status(*req.Status) - statusPtr = &status - } - account, err := h.service.Update(r.Context(), accounts.UpdateAccountInput{ - TenantID: tenantID, - ID: id, - Name: req.Name, - LegalName: req.LegalName, - Document: req.Document, - Website: req.Website, - Industry: req.Industry, - Size: req.Size, - Status: statusPtr, - }) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to update account") - return - } - httpapi.RespondJSON(w, http.StatusOK, account) -} - -func (h *AccountsHandler) Archive(w http.ResponseWriter, r *http.Request) { - id := chi.URLParam(r, "id") - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - if err := h.service.Archive(r.Context(), tenantID, id); err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to archive account") - return - } - httpapi.RespondJSON(w, http.StatusOK, map[string]string{"status": "archived"}) -} diff --git a/crm-core/internal/api/handlers/activities_handler.go b/crm-core/internal/api/handlers/activities_handler.go deleted file mode 100644 index 20a8444..0000000 --- a/crm-core/internal/api/handlers/activities_handler.go +++ /dev/null @@ -1,131 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - "time" - - httpapi "crm-core/internal/api/http" - "crm-core/internal/application/activities" - domain "crm-core/internal/domain/activities" - "crm-core/internal/infrastructure/auth" - - "github.com/go-chi/chi/v5" -) - -type ActivitiesHandler struct { - service *activities.Service -} - -func NewActivitiesHandler(service *activities.Service) *ActivitiesHandler { - return &ActivitiesHandler{service: service} -} - -type activityCreateRequest struct { - Type string `json:"type"` - DealID *string `json:"deal_id"` - AccountID *string `json:"account_id"` - ContactID *string `json:"contact_id"` - Title string `json:"title"` - DueAt *string `json:"due_at"` - AssignedUserID string `json:"assigned_user_id"` -} - -func (h *ActivitiesHandler) Create(w http.ResponseWriter, r *http.Request) { - var req activityCreateRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - httpapi.RespondError(w, http.StatusBadRequest, "invalid payload") - return - } - if req.Title == "" || req.Type == "" { - httpapi.RespondError(w, http.StatusBadRequest, "title and type are required") - return - } - var dueAt *time.Time - if req.DueAt != nil { - parsed, err := time.Parse(time.RFC3339, *req.DueAt) - if err != nil { - httpapi.RespondError(w, http.StatusBadRequest, "due_at must be RFC3339") - return - } - dueAt = &parsed - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - assigned := req.AssignedUserID - if assigned == "" { - assigned, _ = auth.UserIDFromContext(r.Context()) - } - activity, err := h.service.Create(r.Context(), activities.CreateActivityInput{ - TenantID: tenantID, - Type: domain.Type(req.Type), - DealID: req.DealID, - AccountID: req.AccountID, - ContactID: req.ContactID, - Title: req.Title, - DueAt: dueAt, - AssignedUserID: assigned, - }) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to create activity") - return - } - httpapi.RespondJSON(w, http.StatusCreated, activity) -} - -func (h *ActivitiesHandler) List(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - filters := activities.ListFilters{} - if status := r.URL.Query().Get("status"); status != "" { - s := domain.Status(status) - filters.Status = &s - } - if dealID := r.URL.Query().Get("dealId"); dealID != "" { - filters.DealID = &dealID - } - if assignedID := r.URL.Query().Get("assignedUserId"); assignedID != "" { - filters.AssignedUserID = &assignedID - } - items, err := h.service.List(r.Context(), tenantID, filters) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to list activities") - return - } - httpapi.RespondJSON(w, http.StatusOK, items) -} - -func (h *ActivitiesHandler) Complete(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - id := chi.URLParam(r, "id") - activity, err := h.service.Complete(r.Context(), tenantID, id) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to complete activity") - return - } - httpapi.RespondJSON(w, http.StatusOK, activity) -} - -func (h *ActivitiesHandler) Cancel(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - id := chi.URLParam(r, "id") - if err := h.service.Cancel(r.Context(), tenantID, id); err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to cancel activity") - return - } - httpapi.RespondJSON(w, http.StatusOK, map[string]string{"status": "canceled"}) -} diff --git a/crm-core/internal/api/handlers/contacts_handler.go b/crm-core/internal/api/handlers/contacts_handler.go deleted file mode 100644 index 87a8239..0000000 --- a/crm-core/internal/api/handlers/contacts_handler.go +++ /dev/null @@ -1,145 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - - httpapi "crm-core/internal/api/http" - "crm-core/internal/application/contacts" - domain "crm-core/internal/domain/contacts" - "crm-core/internal/infrastructure/auth" - - "github.com/go-chi/chi/v5" -) - -type ContactsHandler struct { - service *contacts.Service -} - -func NewContactsHandler(service *contacts.Service) *ContactsHandler { - return &ContactsHandler{service: service} -} - -type contactCreateRequest struct { - AccountID *string `json:"account_id"` - Name string `json:"name"` - Email *string `json:"email"` - Phone *string `json:"phone"` - RoleTitle *string `json:"role_title"` -} - -func (h *ContactsHandler) Create(w http.ResponseWriter, r *http.Request) { - var req contactCreateRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - httpapi.RespondError(w, http.StatusBadRequest, "invalid payload") - return - } - if req.Name == "" { - httpapi.RespondError(w, http.StatusBadRequest, "name is required") - return - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - contact, err := h.service.Create(r.Context(), contacts.CreateContactInput{ - TenantID: tenantID, - AccountID: req.AccountID, - Name: req.Name, - Email: req.Email, - Phone: req.Phone, - RoleTitle: req.RoleTitle, - }) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to create contact") - return - } - httpapi.RespondJSON(w, http.StatusCreated, contact) -} - -func (h *ContactsHandler) List(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - contactsList, err := h.service.List(r.Context(), tenantID) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to list contacts") - return - } - httpapi.RespondJSON(w, http.StatusOK, contactsList) -} - -func (h *ContactsHandler) Get(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - id := chi.URLParam(r, "id") - contact, err := h.service.Get(r.Context(), tenantID, id) - if err != nil { - httpapi.RespondError(w, http.StatusNotFound, "contact not found") - return - } - httpapi.RespondJSON(w, http.StatusOK, contact) -} - -type contactUpdateRequest struct { - AccountID *string `json:"account_id"` - Name *string `json:"name"` - Email *string `json:"email"` - Phone *string `json:"phone"` - RoleTitle *string `json:"role_title"` - Status *string `json:"status"` -} - -func (h *ContactsHandler) Update(w http.ResponseWriter, r *http.Request) { - var req contactUpdateRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - httpapi.RespondError(w, http.StatusBadRequest, "invalid payload") - return - } - id := chi.URLParam(r, "id") - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - var statusPtr *domain.Status - if req.Status != nil { - status := domain.Status(*req.Status) - statusPtr = &status - } - contact, err := h.service.Update(r.Context(), contacts.UpdateContactInput{ - TenantID: tenantID, - ID: id, - AccountID: req.AccountID, - Name: req.Name, - Email: req.Email, - Phone: req.Phone, - RoleTitle: req.RoleTitle, - Status: statusPtr, - }) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to update contact") - return - } - httpapi.RespondJSON(w, http.StatusOK, contact) -} - -func (h *ContactsHandler) Archive(w http.ResponseWriter, r *http.Request) { - id := chi.URLParam(r, "id") - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - if err := h.service.Archive(r.Context(), tenantID, id); err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to archive contact") - return - } - httpapi.RespondJSON(w, http.StatusOK, map[string]string{"status": "archived"}) -} diff --git a/crm-core/internal/api/handlers/deals_handler.go b/crm-core/internal/api/handlers/deals_handler.go deleted file mode 100644 index a4981c8..0000000 --- a/crm-core/internal/api/handlers/deals_handler.go +++ /dev/null @@ -1,230 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - "time" - - httpapi "crm-core/internal/api/http" - "crm-core/internal/application/deals" - domain "crm-core/internal/domain/deals" - "crm-core/internal/infrastructure/auth" - - "github.com/go-chi/chi/v5" -) - -type DealsHandler struct { - service *deals.Service -} - -func NewDealsHandler(service *deals.Service) *DealsHandler { - return &DealsHandler{service: service} -} - -type dealCreateRequest struct { - Title string `json:"title"` - AccountID *string `json:"account_id"` - ContactID *string `json:"contact_id"` - PipelineID string `json:"pipeline_id"` - StageID string `json:"stage_id"` - ValueCents int64 `json:"value_cents"` - Currency string `json:"currency"` - Source *string `json:"source"` - ExpectedClose *string `json:"expected_close_date"` -} - -func (h *DealsHandler) Create(w http.ResponseWriter, r *http.Request) { - var req dealCreateRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - httpapi.RespondError(w, http.StatusBadRequest, "invalid payload") - return - } - if req.Title == "" || req.PipelineID == "" || req.StageID == "" { - httpapi.RespondError(w, http.StatusBadRequest, "title, pipeline_id, and stage_id are required") - return - } - var expected *time.Time - if req.ExpectedClose != nil { - parsed, err := time.Parse(time.RFC3339, *req.ExpectedClose) - if err != nil { - httpapi.RespondError(w, http.StatusBadRequest, "expected_close_date must be RFC3339") - return - } - expected = &parsed - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - deal, err := h.service.Create(r.Context(), deals.CreateDealInput{ - TenantID: tenantID, - Title: req.Title, - AccountID: req.AccountID, - ContactID: req.ContactID, - PipelineID: req.PipelineID, - StageID: req.StageID, - ValueCents: req.ValueCents, - Currency: req.Currency, - Source: req.Source, - ExpectedClose: expected, - }) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to create deal") - return - } - httpapi.RespondJSON(w, http.StatusCreated, deal) -} - -func (h *DealsHandler) List(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - filters := deals.ListFilters{} - if status := r.URL.Query().Get("status"); status != "" { - s := domain.Status(status) - filters.Status = &s - } - if stageID := r.URL.Query().Get("stageId"); stageID != "" { - filters.StageID = &stageID - } - if accountID := r.URL.Query().Get("accountId"); accountID != "" { - filters.AccountID = &accountID - } - items, err := h.service.List(r.Context(), tenantID, filters) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to list deals") - return - } - httpapi.RespondJSON(w, http.StatusOK, items) -} - -func (h *DealsHandler) Get(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - id := chi.URLParam(r, "id") - deal, err := h.service.Get(r.Context(), tenantID, id) - if err != nil { - httpapi.RespondError(w, http.StatusNotFound, "deal not found") - return - } - httpapi.RespondJSON(w, http.StatusOK, deal) -} - -type dealUpdateRequest struct { - Title *string `json:"title"` - StageID *string `json:"stage_id"` - ValueCents *int64 `json:"value_cents"` - Currency *string `json:"currency"` - Source *string `json:"source"` - ExpectedClose *string `json:"expected_close_date"` -} - -func (h *DealsHandler) Update(w http.ResponseWriter, r *http.Request) { - var req dealUpdateRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - httpapi.RespondError(w, http.StatusBadRequest, "invalid payload") - return - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - id := chi.URLParam(r, "id") - deal, err := h.service.Get(r.Context(), tenantID, id) - if err != nil { - httpapi.RespondError(w, http.StatusNotFound, "deal not found") - return - } - if req.Title != nil { - deal.Title = *req.Title - } - if req.StageID != nil { - deal.StageID = *req.StageID - } - if req.ValueCents != nil { - deal.Value.ValueCents = *req.ValueCents - } - if req.Currency != nil { - deal.Value.Currency = *req.Currency - } - if req.Source != nil { - deal.Source = req.Source - } - if req.ExpectedClose != nil { - parsed, err := time.Parse(time.RFC3339, *req.ExpectedClose) - if err != nil { - httpapi.RespondError(w, http.StatusBadRequest, "expected_close_date must be RFC3339") - return - } - deal.ExpectedClose = &parsed - } - updated, err := h.service.Update(r.Context(), deal) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to update deal") - return - } - httpapi.RespondJSON(w, http.StatusOK, updated) -} - -type moveStageRequest struct { - StageID string `json:"stage_id"` -} - -func (h *DealsHandler) MoveStage(w http.ResponseWriter, r *http.Request) { - var req moveStageRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.StageID == "" { - httpapi.RespondError(w, http.StatusBadRequest, "stage_id is required") - return - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - id := chi.URLParam(r, "id") - deal, err := h.service.MoveStage(r.Context(), tenantID, id, req.StageID) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to move stage") - return - } - httpapi.RespondJSON(w, http.StatusOK, deal) -} - -type closeDealRequest struct { - Status string `json:"status"` -} - -func (h *DealsHandler) Close(w http.ResponseWriter, r *http.Request) { - var req closeDealRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - httpapi.RespondError(w, http.StatusBadRequest, "invalid payload") - return - } - if req.Status != string(domain.StatusWon) && req.Status != string(domain.StatusLost) { - httpapi.RespondError(w, http.StatusBadRequest, "status must be won or lost") - return - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - id := chi.URLParam(r, "id") - deal, err := h.service.Close(r.Context(), deals.CloseInput{ - TenantID: tenantID, - ID: id, - Status: domain.Status(req.Status), - }) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to close deal") - return - } - httpapi.RespondJSON(w, http.StatusOK, deal) -} diff --git a/crm-core/internal/api/handlers/handlers.go b/crm-core/internal/api/handlers/handlers.go deleted file mode 100644 index bb1e03d..0000000 --- a/crm-core/internal/api/handlers/handlers.go +++ /dev/null @@ -1,18 +0,0 @@ -package handlers - -import "net/http" - -type HandlerSet struct { - Accounts *AccountsHandler - Contacts *ContactsHandler - Deals *DealsHandler - Stages *StagesHandler - Activities *ActivitiesHandler - Notes *NotesHandler - Tags *TagsHandler -} - -func (h HandlerSet) Health(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("ok")) -} diff --git a/crm-core/internal/api/handlers/notes_handler.go b/crm-core/internal/api/handlers/notes_handler.go deleted file mode 100644 index 8551866..0000000 --- a/crm-core/internal/api/handlers/notes_handler.go +++ /dev/null @@ -1,82 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - - httpapi "crm-core/internal/api/http" - "crm-core/internal/application/notes" - "crm-core/internal/infrastructure/auth" -) - -type NotesHandler struct { - service *notes.Service -} - -func NewNotesHandler(service *notes.Service) *NotesHandler { - return &NotesHandler{service: service} -} - -type noteCreateRequest struct { - EntityType string `json:"entity_type"` - EntityID string `json:"entity_id"` - Body string `json:"body"` -} - -func (h *NotesHandler) Create(w http.ResponseWriter, r *http.Request) { - var req noteCreateRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - httpapi.RespondError(w, http.StatusBadRequest, "invalid payload") - return - } - if req.EntityType == "" || req.EntityID == "" || req.Body == "" { - httpapi.RespondError(w, http.StatusBadRequest, "entity_type, entity_id, and body are required") - return - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - userID, err := auth.UserIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid user") - return - } - note, err := h.service.Add(r.Context(), notes.AddNoteInput{ - TenantID: tenantID, - EntityType: req.EntityType, - EntityID: req.EntityID, - Body: req.Body, - CreatedByUserID: userID, - }) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to add note") - return - } - httpapi.RespondJSON(w, http.StatusCreated, note) -} - -func (h *NotesHandler) List(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - entityType := r.URL.Query().Get("entityType") - entityID := r.URL.Query().Get("entityId") - var entityTypePtr *string - var entityIDPtr *string - if entityType != "" { - entityTypePtr = &entityType - } - if entityID != "" { - entityIDPtr = &entityID - } - notesList, err := h.service.List(r.Context(), tenantID, entityTypePtr, entityIDPtr) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to list notes") - return - } - httpapi.RespondJSON(w, http.StatusOK, notesList) -} diff --git a/crm-core/internal/api/handlers/stages_handler.go b/crm-core/internal/api/handlers/stages_handler.go deleted file mode 100644 index 3f24575..0000000 --- a/crm-core/internal/api/handlers/stages_handler.go +++ /dev/null @@ -1,111 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - - httpapi "crm-core/internal/api/http" - "crm-core/internal/application/stages" - "crm-core/internal/infrastructure/auth" - - "github.com/go-chi/chi/v5" -) - -type StagesHandler struct { - service *stages.Service -} - -func NewStagesHandler(service *stages.Service) *StagesHandler { - return &StagesHandler{service: service} -} - -type pipelineCreateRequest struct { - Name string `json:"name"` - IsDefault bool `json:"is_default"` -} - -func (h *StagesHandler) CreatePipeline(w http.ResponseWriter, r *http.Request) { - var req pipelineCreateRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" { - httpapi.RespondError(w, http.StatusBadRequest, "invalid payload") - return - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - pipeline, err := h.service.CreatePipeline(r.Context(), stages.CreatePipelineInput{ - TenantID: tenantID, - Name: req.Name, - IsDefault: req.IsDefault, - }) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to create pipeline") - return - } - httpapi.RespondJSON(w, http.StatusCreated, pipeline) -} - -func (h *StagesHandler) ListPipelines(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - pipelines, err := h.service.ListPipelines(r.Context(), tenantID) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to list pipelines") - return - } - httpapi.RespondJSON(w, http.StatusOK, pipelines) -} - -type stageCreateRequest struct { - Name string `json:"name"` - OrderIndex int32 `json:"order_index"` - IsWon bool `json:"is_won"` - IsLost bool `json:"is_lost"` -} - -func (h *StagesHandler) CreateStage(w http.ResponseWriter, r *http.Request) { - var req stageCreateRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" { - httpapi.RespondError(w, http.StatusBadRequest, "invalid payload") - return - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - pipelineID := chi.URLParam(r, "id") - stage, err := h.service.CreateStage(r.Context(), stages.CreateStageInput{ - TenantID: tenantID, - PipelineID: pipelineID, - Name: req.Name, - OrderIndex: req.OrderIndex, - IsWon: req.IsWon, - IsLost: req.IsLost, - }) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to create stage") - return - } - httpapi.RespondJSON(w, http.StatusCreated, stage) -} - -func (h *StagesHandler) ListStages(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - pipelineID := chi.URLParam(r, "id") - items, err := h.service.ListStages(r.Context(), tenantID, pipelineID) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to list stages") - return - } - httpapi.RespondJSON(w, http.StatusOK, items) -} diff --git a/crm-core/internal/api/handlers/tags_handler.go b/crm-core/internal/api/handlers/tags_handler.go deleted file mode 100644 index b852095..0000000 --- a/crm-core/internal/api/handlers/tags_handler.go +++ /dev/null @@ -1,110 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - - httpapi "crm-core/internal/api/http" - "crm-core/internal/application/tags" - "crm-core/internal/infrastructure/auth" -) - -type TagsHandler struct { - service *tags.Service -} - -func NewTagsHandler(service *tags.Service) *TagsHandler { - return &TagsHandler{service: service} -} - -type tagUpsertRequest struct { - Name string `json:"name"` -} - -func (h *TagsHandler) Upsert(w http.ResponseWriter, r *http.Request) { - var req tagUpsertRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" { - httpapi.RespondError(w, http.StatusBadRequest, "name is required") - return - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - tag, err := h.service.Upsert(r.Context(), tags.UpsertTagInput{ - TenantID: tenantID, - Name: req.Name, - }) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to upsert tag") - return - } - httpapi.RespondJSON(w, http.StatusCreated, tag) -} - -func (h *TagsHandler) List(w http.ResponseWriter, r *http.Request) { - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - items, err := h.service.List(r.Context(), tenantID) - if err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to list tags") - return - } - httpapi.RespondJSON(w, http.StatusOK, items) -} - -type tagAssignRequest struct { - EntityType string `json:"entity_type"` - EntityID string `json:"entity_id"` - TagID string `json:"tag_id"` -} - -func (h *TagsHandler) Assign(w http.ResponseWriter, r *http.Request) { - var req tagAssignRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.EntityType == "" || req.EntityID == "" || req.TagID == "" { - httpapi.RespondError(w, http.StatusBadRequest, "entity_type, entity_id and tag_id are required") - return - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - if err := h.service.Assign(r.Context(), tags.TagEntityInput{ - TenantID: tenantID, - EntityType: req.EntityType, - EntityID: req.EntityID, - TagID: req.TagID, - }); err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to assign tag") - return - } - httpapi.RespondJSON(w, http.StatusOK, map[string]string{"status": "assigned"}) -} - -func (h *TagsHandler) Unassign(w http.ResponseWriter, r *http.Request) { - var req tagAssignRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.EntityType == "" || req.EntityID == "" || req.TagID == "" { - httpapi.RespondError(w, http.StatusBadRequest, "entity_type, entity_id and tag_id are required") - return - } - tenantID, err := auth.TenantIDFromContext(r.Context()) - if err != nil { - httpapi.RespondError(w, http.StatusUnauthorized, "invalid tenant") - return - } - if err := h.service.Unassign(r.Context(), tags.TagEntityInput{ - TenantID: tenantID, - EntityType: req.EntityType, - EntityID: req.EntityID, - TagID: req.TagID, - }); err != nil { - httpapi.RespondError(w, http.StatusInternalServerError, "failed to unassign tag") - return - } - httpapi.RespondJSON(w, http.StatusOK, map[string]string{"status": "unassigned"}) -} diff --git a/crm-core/internal/api/http/middleware.go b/crm-core/internal/api/http/middleware.go deleted file mode 100644 index f3b0848..0000000 --- a/crm-core/internal/api/http/middleware.go +++ /dev/null @@ -1,62 +0,0 @@ -package httpapi - -import ( - "context" - "log/slog" - "net/http" - "time" - - "github.com/google/uuid" -) - -type ctxKey string - -const requestIDKey ctxKey = "request_id" - -func RequestID(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestID := r.Header.Get("X-Request-Id") - if requestID == "" { - requestID = uuid.NewString() - } - w.Header().Set("X-Request-Id", requestID) - ctx := context.WithValue(r.Context(), requestIDKey, requestID) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -func RequestIDFromContext(ctx context.Context) string { - val := ctx.Value(requestIDKey) - if val == nil { - return "" - } - id, _ := val.(string) - return id -} - -func Logging(logger *slog.Logger) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - ww := &responseWriter{ResponseWriter: w, status: http.StatusOK} - next.ServeHTTP(ww, r) - logger.Info("request", - "method", r.Method, - "path", r.URL.Path, - "status", ww.status, - "duration_ms", time.Since(start).Milliseconds(), - "request_id", RequestIDFromContext(r.Context()), - ) - }) - } -} - -type responseWriter struct { - http.ResponseWriter - status int -} - -func (w *responseWriter) WriteHeader(status int) { - w.status = status - w.ResponseWriter.WriteHeader(status) -} diff --git a/crm-core/internal/api/http/response.go b/crm-core/internal/api/http/response.go deleted file mode 100644 index 134426f..0000000 --- a/crm-core/internal/api/http/response.go +++ /dev/null @@ -1,23 +0,0 @@ -package httpapi - -import ( - "encoding/json" - "net/http" -) - -type ErrorResponse struct { - Message string `json:"message"` -} - -func RespondJSON(w http.ResponseWriter, status int, payload any) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - if payload == nil { - return - } - _ = json.NewEncoder(w).Encode(payload) -} - -func RespondError(w http.ResponseWriter, status int, message string) { - RespondJSON(w, status, ErrorResponse{Message: message}) -} diff --git a/crm-core/internal/api/router/router.go b/crm-core/internal/api/router/router.go deleted file mode 100644 index 66e03e9..0000000 --- a/crm-core/internal/api/router/router.go +++ /dev/null @@ -1,79 +0,0 @@ -package router - -import ( - "net/http" - - httpapi "crm-core/internal/api/http" - "crm-core/internal/api/handlers" - "crm-core/internal/infrastructure/auth" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "log/slog" -) - -func NewRouter(logger *slog.Logger, authMiddleware *auth.Middleware, handlerSet handlers.HandlerSet) http.Handler { - r := chi.NewRouter() - r.Use(middleware.Recoverer) - r.Use(httpapi.RequestID) - r.Use(httpapi.Logging(logger)) - - r.Get("/health", handlerSet.Health) - - r.Route("/api/v1", func(r chi.Router) { - r.Use(authMiddleware.RequireJWT) - - r.Route("/accounts", func(r chi.Router) { - r.With(auth.RequireScopes("crm.read")).Get("/", handlerSet.Accounts.List) - r.With(auth.RequireScopes("crm.read")).Get("/{id}", handlerSet.Accounts.Get) - r.With(auth.RequireScopes("crm.write")).Post("/", handlerSet.Accounts.Create) - r.With(auth.RequireScopes("crm.write")).Patch("/{id}", handlerSet.Accounts.Update) - r.With(auth.RequireScopes("crm.write")).Delete("/{id}", handlerSet.Accounts.Archive) - }) - - r.Route("/contacts", func(r chi.Router) { - r.With(auth.RequireScopes("crm.read")).Get("/", handlerSet.Contacts.List) - r.With(auth.RequireScopes("crm.read")).Get("/{id}", handlerSet.Contacts.Get) - r.With(auth.RequireScopes("crm.write")).Post("/", handlerSet.Contacts.Create) - r.With(auth.RequireScopes("crm.write")).Patch("/{id}", handlerSet.Contacts.Update) - r.With(auth.RequireScopes("crm.write")).Delete("/{id}", handlerSet.Contacts.Archive) - }) - - r.Route("/pipelines", func(r chi.Router) { - r.With(auth.RequireScopes("crm.read")).Get("/", handlerSet.Stages.ListPipelines) - r.With(auth.RequireScopes("crm.write")).Post("/", handlerSet.Stages.CreatePipeline) - r.With(auth.RequireScopes("crm.read")).Get("/{id}/stages", handlerSet.Stages.ListStages) - r.With(auth.RequireScopes("crm.write")).Post("/{id}/stages", handlerSet.Stages.CreateStage) - }) - - r.Route("/deals", func(r chi.Router) { - r.With(auth.RequireScopes("crm.read")).Get("/", handlerSet.Deals.List) - r.With(auth.RequireScopes("crm.read")).Get("/{id}", handlerSet.Deals.Get) - r.With(auth.RequireScopes("crm.write")).Post("/", handlerSet.Deals.Create) - r.With(auth.RequireScopes("crm.write")).Patch("/{id}", handlerSet.Deals.Update) - r.With(auth.RequireScopes("crm.write")).Post("/{id}/move-stage", handlerSet.Deals.MoveStage) - r.With(auth.RequireScopes("crm.write")).Post("/{id}/close", handlerSet.Deals.Close) - }) - - r.Route("/activities", func(r chi.Router) { - r.With(auth.RequireScopes("crm.read")).Get("/", handlerSet.Activities.List) - r.With(auth.RequireScopes("crm.write")).Post("/", handlerSet.Activities.Create) - r.With(auth.RequireScopes("crm.write")).Post("/{id}/complete", handlerSet.Activities.Complete) - r.With(auth.RequireScopes("crm.write")).Delete("/{id}", handlerSet.Activities.Cancel) - }) - - r.Route("/notes", func(r chi.Router) { - r.With(auth.RequireScopes("crm.read")).Get("/", handlerSet.Notes.List) - r.With(auth.RequireScopes("crm.write")).Post("/", handlerSet.Notes.Create) - }) - - r.Route("/tags", func(r chi.Router) { - r.With(auth.RequireScopes("crm.read")).Get("/", handlerSet.Tags.List) - r.With(auth.RequireScopes("crm.write")).Post("/", handlerSet.Tags.Upsert) - r.With(auth.RequireScopes("crm.write")).Post("/assign", handlerSet.Tags.Assign) - r.With(auth.RequireScopes("crm.write")).Post("/unassign", handlerSet.Tags.Unassign) - }) - }) - - return r -} diff --git a/crm-core/internal/application/accounts/archive_account.go b/crm-core/internal/application/accounts/archive_account.go deleted file mode 100644 index 42b25a4..0000000 --- a/crm-core/internal/application/accounts/archive_account.go +++ /dev/null @@ -1,7 +0,0 @@ -package accounts - -import "context" - -func (s *Service) Archive(ctx context.Context, tenantID, id string) error { - return s.repo.Archive(ctx, tenantID, id) -} diff --git a/crm-core/internal/application/accounts/create_account.go b/crm-core/internal/application/accounts/create_account.go deleted file mode 100644 index da3c8ee..0000000 --- a/crm-core/internal/application/accounts/create_account.go +++ /dev/null @@ -1,35 +0,0 @@ -package accounts - -import ( - "context" - - "crm-core/internal/domain/accounts" - "crm-core/internal/domain/common" -) - -type CreateAccountInput struct { - TenantID string - Name string - LegalName *string - Document *string - Website *string - Industry *string - Size *string - ExternalRefs map[string]string -} - -func (s *Service) Create(ctx context.Context, input CreateAccountInput) (accounts.Account, error) { - account := accounts.Account{ - ID: common.NewID(), - TenantID: input.TenantID, - Name: input.Name, - LegalName: input.LegalName, - Document: input.Document, - Website: input.Website, - Industry: input.Industry, - Size: input.Size, - Status: accounts.StatusActive, - ExternalRefs: input.ExternalRefs, - } - return s.repo.Create(ctx, account) -} diff --git a/crm-core/internal/application/accounts/list_accounts.go b/crm-core/internal/application/accounts/list_accounts.go deleted file mode 100644 index 4ecb983..0000000 --- a/crm-core/internal/application/accounts/list_accounts.go +++ /dev/null @@ -1,15 +0,0 @@ -package accounts - -import ( - "context" - - "crm-core/internal/domain/accounts" -) - -func (s *Service) List(ctx context.Context, tenantID string) ([]accounts.Account, error) { - return s.repo.List(ctx, tenantID) -} - -func (s *Service) Get(ctx context.Context, tenantID, id string) (accounts.Account, error) { - return s.repo.Get(ctx, tenantID, id) -} diff --git a/crm-core/internal/application/accounts/service.go b/crm-core/internal/application/accounts/service.go deleted file mode 100644 index 2026857..0000000 --- a/crm-core/internal/application/accounts/service.go +++ /dev/null @@ -1,23 +0,0 @@ -package accounts - -import ( - "context" - - "crm-core/internal/domain/accounts" -) - -type Repository interface { - Create(ctx context.Context, account accounts.Account) (accounts.Account, error) - Update(ctx context.Context, account accounts.Account) (accounts.Account, error) - List(ctx context.Context, tenantID string) ([]accounts.Account, error) - Get(ctx context.Context, tenantID, id string) (accounts.Account, error) - Archive(ctx context.Context, tenantID, id string) error -} - -type Service struct { - repo Repository -} - -func NewService(repo Repository) *Service { - return &Service{repo: repo} -} diff --git a/crm-core/internal/application/accounts/update_account.go b/crm-core/internal/application/accounts/update_account.go deleted file mode 100644 index c4f81d4..0000000 --- a/crm-core/internal/application/accounts/update_account.go +++ /dev/null @@ -1,48 +0,0 @@ -package accounts - -import ( - "context" - - "crm-core/internal/domain/accounts" -) - -type UpdateAccountInput struct { - TenantID string - ID string - Name *string - LegalName *string - Document *string - Website *string - Industry *string - Size *string - Status *accounts.Status -} - -func (s *Service) Update(ctx context.Context, input UpdateAccountInput) (accounts.Account, error) { - account, err := s.repo.Get(ctx, input.TenantID, input.ID) - if err != nil { - return accounts.Account{}, err - } - if input.Name != nil { - account.Name = *input.Name - } - if input.LegalName != nil { - account.LegalName = input.LegalName - } - if input.Document != nil { - account.Document = input.Document - } - if input.Website != nil { - account.Website = input.Website - } - if input.Industry != nil { - account.Industry = input.Industry - } - if input.Size != nil { - account.Size = input.Size - } - if input.Status != nil { - account.Status = *input.Status - } - return s.repo.Update(ctx, account) -} diff --git a/crm-core/internal/application/activities/create_activity.go b/crm-core/internal/application/activities/create_activity.go deleted file mode 100644 index 36d1135..0000000 --- a/crm-core/internal/application/activities/create_activity.go +++ /dev/null @@ -1,36 +0,0 @@ -package activities - -import ( - "context" - "time" - - "crm-core/internal/domain/activities" - "crm-core/internal/domain/common" -) - -type CreateActivityInput struct { - TenantID string - Type activities.Type - DealID *string - AccountID *string - ContactID *string - Title string - DueAt *time.Time - AssignedUserID string -} - -func (s *Service) Create(ctx context.Context, input CreateActivityInput) (activities.Activity, error) { - activity := activities.Activity{ - ID: common.NewID(), - TenantID: input.TenantID, - Type: input.Type, - DealID: input.DealID, - AccountID: input.AccountID, - ContactID: input.ContactID, - Title: input.Title, - DueAt: input.DueAt, - Status: activities.StatusOpen, - AssignedUserID: input.AssignedUserID, - } - return s.repo.Create(ctx, activity) -} diff --git a/crm-core/internal/application/activities/list_activities.go b/crm-core/internal/application/activities/list_activities.go deleted file mode 100644 index 5d07e82..0000000 --- a/crm-core/internal/application/activities/list_activities.go +++ /dev/null @@ -1,19 +0,0 @@ -package activities - -import ( - "context" - - "crm-core/internal/domain/activities" -) - -func (s *Service) List(ctx context.Context, tenantID string, filters ListFilters) ([]activities.Activity, error) { - return s.repo.List(ctx, tenantID, filters) -} - -func (s *Service) Complete(ctx context.Context, tenantID, id string) (activities.Activity, error) { - return s.repo.Complete(ctx, tenantID, id) -} - -func (s *Service) Cancel(ctx context.Context, tenantID, id string) error { - return s.repo.Cancel(ctx, tenantID, id) -} diff --git a/crm-core/internal/application/activities/service.go b/crm-core/internal/application/activities/service.go deleted file mode 100644 index fcd0719..0000000 --- a/crm-core/internal/application/activities/service.go +++ /dev/null @@ -1,28 +0,0 @@ -package activities - -import ( - "context" - - "crm-core/internal/domain/activities" -) - -type Repository interface { - Create(ctx context.Context, activity activities.Activity) (activities.Activity, error) - List(ctx context.Context, tenantID string, filters ListFilters) ([]activities.Activity, error) - Complete(ctx context.Context, tenantID, id string) (activities.Activity, error) - Cancel(ctx context.Context, tenantID, id string) error -} - -type Service struct { - repo Repository -} - -func NewService(repo Repository) *Service { - return &Service{repo: repo} -} - -type ListFilters struct { - Status *activities.Status - DealID *string - AssignedUserID *string -} diff --git a/crm-core/internal/application/contacts/create_contact.go b/crm-core/internal/application/contacts/create_contact.go deleted file mode 100644 index c16e996..0000000 --- a/crm-core/internal/application/contacts/create_contact.go +++ /dev/null @@ -1,31 +0,0 @@ -package contacts - -import ( - "context" - - "crm-core/internal/domain/common" - "crm-core/internal/domain/contacts" -) - -type CreateContactInput struct { - TenantID string - AccountID *string - Name string - Email *string - Phone *string - RoleTitle *string -} - -func (s *Service) Create(ctx context.Context, input CreateContactInput) (contacts.Contact, error) { - contact := contacts.Contact{ - ID: common.NewID(), - TenantID: input.TenantID, - AccountID: input.AccountID, - Name: input.Name, - Email: input.Email, - Phone: input.Phone, - RoleTitle: input.RoleTitle, - Status: contacts.StatusActive, - } - return s.repo.Create(ctx, contact) -} diff --git a/crm-core/internal/application/contacts/list_contacts.go b/crm-core/internal/application/contacts/list_contacts.go deleted file mode 100644 index a130bae..0000000 --- a/crm-core/internal/application/contacts/list_contacts.go +++ /dev/null @@ -1,19 +0,0 @@ -package contacts - -import ( - "context" - - "crm-core/internal/domain/contacts" -) - -func (s *Service) List(ctx context.Context, tenantID string) ([]contacts.Contact, error) { - return s.repo.List(ctx, tenantID) -} - -func (s *Service) Get(ctx context.Context, tenantID, id string) (contacts.Contact, error) { - return s.repo.Get(ctx, tenantID, id) -} - -func (s *Service) Archive(ctx context.Context, tenantID, id string) error { - return s.repo.Archive(ctx, tenantID, id) -} diff --git a/crm-core/internal/application/contacts/service.go b/crm-core/internal/application/contacts/service.go deleted file mode 100644 index f8592a2..0000000 --- a/crm-core/internal/application/contacts/service.go +++ /dev/null @@ -1,23 +0,0 @@ -package contacts - -import ( - "context" - - "crm-core/internal/domain/contacts" -) - -type Repository interface { - Create(ctx context.Context, contact contacts.Contact) (contacts.Contact, error) - Update(ctx context.Context, contact contacts.Contact) (contacts.Contact, error) - List(ctx context.Context, tenantID string) ([]contacts.Contact, error) - Get(ctx context.Context, tenantID, id string) (contacts.Contact, error) - Archive(ctx context.Context, tenantID, id string) error -} - -type Service struct { - repo Repository -} - -func NewService(repo Repository) *Service { - return &Service{repo: repo} -} diff --git a/crm-core/internal/application/contacts/update_contact.go b/crm-core/internal/application/contacts/update_contact.go deleted file mode 100644 index 1a19c2f..0000000 --- a/crm-core/internal/application/contacts/update_contact.go +++ /dev/null @@ -1,44 +0,0 @@ -package contacts - -import ( - "context" - - "crm-core/internal/domain/contacts" -) - -type UpdateContactInput struct { - TenantID string - ID string - AccountID *string - Name *string - Email *string - Phone *string - RoleTitle *string - Status *contacts.Status -} - -func (s *Service) Update(ctx context.Context, input UpdateContactInput) (contacts.Contact, error) { - contact, err := s.repo.Get(ctx, input.TenantID, input.ID) - if err != nil { - return contacts.Contact{}, err - } - if input.AccountID != nil { - contact.AccountID = input.AccountID - } - if input.Name != nil { - contact.Name = *input.Name - } - if input.Email != nil { - contact.Email = input.Email - } - if input.Phone != nil { - contact.Phone = input.Phone - } - if input.RoleTitle != nil { - contact.RoleTitle = input.RoleTitle - } - if input.Status != nil { - contact.Status = *input.Status - } - return s.repo.Update(ctx, contact) -} diff --git a/crm-core/internal/application/deals/close_deal.go b/crm-core/internal/application/deals/close_deal.go deleted file mode 100644 index 1ce84d7..0000000 --- a/crm-core/internal/application/deals/close_deal.go +++ /dev/null @@ -1,17 +0,0 @@ -package deals - -import ( - "context" - - domain "crm-core/internal/domain/deals" -) - -type CloseInput struct { - TenantID string - ID string - Status domain.Status -} - -func (s *Service) Close(ctx context.Context, input CloseInput) (domain.Deal, error) { - return s.repo.Close(ctx, input.TenantID, input.ID, input.Status) -} diff --git a/crm-core/internal/application/deals/create_deal.go b/crm-core/internal/application/deals/create_deal.go deleted file mode 100644 index 26249df..0000000 --- a/crm-core/internal/application/deals/create_deal.go +++ /dev/null @@ -1,46 +0,0 @@ -package deals - -import ( - "context" - "time" - - "crm-core/internal/domain/common" - "crm-core/internal/domain/deals" -) - -type CreateDealInput struct { - TenantID string - Title string - AccountID *string - ContactID *string - PipelineID string - StageID string - ValueCents int64 - Currency string - Source *string - ExpectedClose *time.Time -} - -func (s *Service) Create(ctx context.Context, input CreateDealInput) (deals.Deal, error) { - currency := input.Currency - if currency == "" { - currency = "BRL" - } - deal := deals.Deal{ - ID: common.NewID(), - TenantID: input.TenantID, - Title: input.Title, - AccountID: input.AccountID, - ContactID: input.ContactID, - PipelineID: input.PipelineID, - StageID: input.StageID, - Value: deals.Money{ - ValueCents: input.ValueCents, - Currency: currency, - }, - Status: deals.StatusOpen, - Source: input.Source, - ExpectedClose: input.ExpectedClose, - } - return s.repo.Create(ctx, deal) -} diff --git a/crm-core/internal/application/deals/list_deals.go b/crm-core/internal/application/deals/list_deals.go deleted file mode 100644 index 3139ebe..0000000 --- a/crm-core/internal/application/deals/list_deals.go +++ /dev/null @@ -1,19 +0,0 @@ -package deals - -import ( - "context" - - domain "crm-core/internal/domain/deals" -) - -func (s *Service) List(ctx context.Context, tenantID string, filters ListFilters) ([]domain.Deal, error) { - return s.repo.List(ctx, tenantID, filters) -} - -func (s *Service) Get(ctx context.Context, tenantID, id string) (domain.Deal, error) { - return s.repo.Get(ctx, tenantID, id) -} - -func (s *Service) Update(ctx context.Context, deal domain.Deal) (domain.Deal, error) { - return s.repo.Update(ctx, deal) -} diff --git a/crm-core/internal/application/deals/move_deal_stage.go b/crm-core/internal/application/deals/move_deal_stage.go deleted file mode 100644 index 82274d1..0000000 --- a/crm-core/internal/application/deals/move_deal_stage.go +++ /dev/null @@ -1,11 +0,0 @@ -package deals - -import ( - "context" - - domain "crm-core/internal/domain/deals" -) - -func (s *Service) MoveStage(ctx context.Context, tenantID, id, stageID string) (domain.Deal, error) { - return s.repo.MoveStage(ctx, tenantID, id, stageID) -} diff --git a/crm-core/internal/application/deals/service.go b/crm-core/internal/application/deals/service.go deleted file mode 100644 index 7371da5..0000000 --- a/crm-core/internal/application/deals/service.go +++ /dev/null @@ -1,30 +0,0 @@ -package deals - -import ( - "context" - - "crm-core/internal/domain/deals" -) - -type Repository interface { - Create(ctx context.Context, deal deals.Deal) (deals.Deal, error) - Update(ctx context.Context, deal deals.Deal) (deals.Deal, error) - Get(ctx context.Context, tenantID, id string) (deals.Deal, error) - List(ctx context.Context, tenantID string, filters ListFilters) ([]deals.Deal, error) - MoveStage(ctx context.Context, tenantID, id, stageID string) (deals.Deal, error) - Close(ctx context.Context, tenantID, id string, status deals.Status) (deals.Deal, error) -} - -type Service struct { - repo Repository -} - -func NewService(repo Repository) *Service { - return &Service{repo: repo} -} - -type ListFilters struct { - Status *deals.Status - StageID *string - AccountID *string -} diff --git a/crm-core/internal/application/notes/add_note.go b/crm-core/internal/application/notes/add_note.go deleted file mode 100644 index 5d3524a..0000000 --- a/crm-core/internal/application/notes/add_note.go +++ /dev/null @@ -1,28 +0,0 @@ -package notes - -import ( - "context" - - "crm-core/internal/domain/common" - "crm-core/internal/domain/notes" -) - -type AddNoteInput struct { - TenantID string - EntityType string - EntityID string - Body string - CreatedByUserID string -} - -func (s *Service) Add(ctx context.Context, input AddNoteInput) (notes.Note, error) { - note := notes.Note{ - ID: common.NewID(), - TenantID: input.TenantID, - EntityType: input.EntityType, - EntityID: input.EntityID, - Body: input.Body, - CreatedByUserID: input.CreatedByUserID, - } - return s.repo.Create(ctx, note) -} diff --git a/crm-core/internal/application/notes/list_notes.go b/crm-core/internal/application/notes/list_notes.go deleted file mode 100644 index 0e02bc2..0000000 --- a/crm-core/internal/application/notes/list_notes.go +++ /dev/null @@ -1,11 +0,0 @@ -package notes - -import ( - "context" - - "crm-core/internal/domain/notes" -) - -func (s *Service) List(ctx context.Context, tenantID string, entityType *string, entityID *string) ([]notes.Note, error) { - return s.repo.List(ctx, tenantID, entityType, entityID) -} diff --git a/crm-core/internal/application/notes/service.go b/crm-core/internal/application/notes/service.go deleted file mode 100644 index 2caed12..0000000 --- a/crm-core/internal/application/notes/service.go +++ /dev/null @@ -1,20 +0,0 @@ -package notes - -import ( - "context" - - "crm-core/internal/domain/notes" -) - -type Repository interface { - Create(ctx context.Context, note notes.Note) (notes.Note, error) - List(ctx context.Context, tenantID string, entityType *string, entityID *string) ([]notes.Note, error) -} - -type Service struct { - repo Repository -} - -func NewService(repo Repository) *Service { - return &Service{repo: repo} -} diff --git a/crm-core/internal/application/stages/create_pipeline.go b/crm-core/internal/application/stages/create_pipeline.go deleted file mode 100644 index 665aaac..0000000 --- a/crm-core/internal/application/stages/create_pipeline.go +++ /dev/null @@ -1,24 +0,0 @@ -package stages - -import ( - "context" - - "crm-core/internal/domain/common" - "crm-core/internal/domain/pipelines" -) - -type CreatePipelineInput struct { - TenantID string - Name string - IsDefault bool -} - -func (s *Service) CreatePipeline(ctx context.Context, input CreatePipelineInput) (pipelines.Pipeline, error) { - pipeline := pipelines.Pipeline{ - ID: common.NewID(), - TenantID: input.TenantID, - Name: input.Name, - IsDefault: input.IsDefault, - } - return s.repo.CreatePipeline(ctx, pipeline) -} diff --git a/crm-core/internal/application/stages/create_stage.go b/crm-core/internal/application/stages/create_stage.go deleted file mode 100644 index c9d38f8..0000000 --- a/crm-core/internal/application/stages/create_stage.go +++ /dev/null @@ -1,30 +0,0 @@ -package stages - -import ( - "context" - - "crm-core/internal/domain/common" - "crm-core/internal/domain/pipelines" -) - -type CreateStageInput struct { - TenantID string - PipelineID string - Name string - OrderIndex int32 - IsWon bool - IsLost bool -} - -func (s *Service) CreateStage(ctx context.Context, input CreateStageInput) (pipelines.Stage, error) { - stage := pipelines.Stage{ - ID: common.NewID(), - TenantID: input.TenantID, - PipelineID: input.PipelineID, - Name: input.Name, - OrderIndex: input.OrderIndex, - IsWon: input.IsWon, - IsLost: input.IsLost, - } - return s.repo.CreateStage(ctx, stage) -} diff --git a/crm-core/internal/application/stages/list_stages.go b/crm-core/internal/application/stages/list_stages.go deleted file mode 100644 index 4c440dd..0000000 --- a/crm-core/internal/application/stages/list_stages.go +++ /dev/null @@ -1,15 +0,0 @@ -package stages - -import ( - "context" - - "crm-core/internal/domain/pipelines" -) - -func (s *Service) ListPipelines(ctx context.Context, tenantID string) ([]pipelines.Pipeline, error) { - return s.repo.ListPipelines(ctx, tenantID) -} - -func (s *Service) ListStages(ctx context.Context, tenantID, pipelineID string) ([]pipelines.Stage, error) { - return s.repo.ListStages(ctx, tenantID, pipelineID) -} diff --git a/crm-core/internal/application/stages/service.go b/crm-core/internal/application/stages/service.go deleted file mode 100644 index 50954ce..0000000 --- a/crm-core/internal/application/stages/service.go +++ /dev/null @@ -1,22 +0,0 @@ -package stages - -import ( - "context" - - "crm-core/internal/domain/pipelines" -) - -type Repository interface { - CreatePipeline(ctx context.Context, pipeline pipelines.Pipeline) (pipelines.Pipeline, error) - ListPipelines(ctx context.Context, tenantID string) ([]pipelines.Pipeline, error) - CreateStage(ctx context.Context, stage pipelines.Stage) (pipelines.Stage, error) - ListStages(ctx context.Context, tenantID, pipelineID string) ([]pipelines.Stage, error) -} - -type Service struct { - repo Repository -} - -func NewService(repo Repository) *Service { - return &Service{repo: repo} -} diff --git a/crm-core/internal/application/tags/list_tags.go b/crm-core/internal/application/tags/list_tags.go deleted file mode 100644 index d386bfe..0000000 --- a/crm-core/internal/application/tags/list_tags.go +++ /dev/null @@ -1,11 +0,0 @@ -package tags - -import ( - "context" - - "crm-core/internal/domain/tags" -) - -func (s *Service) List(ctx context.Context, tenantID string) ([]tags.Tag, error) { - return s.repo.List(ctx, tenantID) -} diff --git a/crm-core/internal/application/tags/service.go b/crm-core/internal/application/tags/service.go deleted file mode 100644 index e5861d2..0000000 --- a/crm-core/internal/application/tags/service.go +++ /dev/null @@ -1,22 +0,0 @@ -package tags - -import ( - "context" - - "crm-core/internal/domain/tags" -) - -type Repository interface { - Upsert(ctx context.Context, tag tags.Tag) (tags.Tag, error) - List(ctx context.Context, tenantID string) ([]tags.Tag, error) - Assign(ctx context.Context, tenantID, entityType, entityID, tagID string) error - Unassign(ctx context.Context, tenantID, entityType, entityID, tagID string) error -} - -type Service struct { - repo Repository -} - -func NewService(repo Repository) *Service { - return &Service{repo: repo} -} diff --git a/crm-core/internal/application/tags/tag_entity.go b/crm-core/internal/application/tags/tag_entity.go deleted file mode 100644 index d1951c0..0000000 --- a/crm-core/internal/application/tags/tag_entity.go +++ /dev/null @@ -1,18 +0,0 @@ -package tags - -import "context" - -type TagEntityInput struct { - TenantID string - EntityType string - EntityID string - TagID string -} - -func (s *Service) Assign(ctx context.Context, input TagEntityInput) error { - return s.repo.Assign(ctx, input.TenantID, input.EntityType, input.EntityID, input.TagID) -} - -func (s *Service) Unassign(ctx context.Context, input TagEntityInput) error { - return s.repo.Unassign(ctx, input.TenantID, input.EntityType, input.EntityID, input.TagID) -} diff --git a/crm-core/internal/application/tags/upsert_tags.go b/crm-core/internal/application/tags/upsert_tags.go deleted file mode 100644 index a3b9125..0000000 --- a/crm-core/internal/application/tags/upsert_tags.go +++ /dev/null @@ -1,22 +0,0 @@ -package tags - -import ( - "context" - - "crm-core/internal/domain/common" - "crm-core/internal/domain/tags" -) - -type UpsertTagInput struct { - TenantID string - Name string -} - -func (s *Service) Upsert(ctx context.Context, input UpsertTagInput) (tags.Tag, error) { - tag := tags.Tag{ - ID: common.NewID(), - TenantID: input.TenantID, - Name: input.Name, - } - return s.repo.Upsert(ctx, tag) -} diff --git a/crm-core/internal/config/config.go b/crm-core/internal/config/config.go deleted file mode 100644 index 9ba69b5..0000000 --- a/crm-core/internal/config/config.go +++ /dev/null @@ -1,74 +0,0 @@ -package config - -import ( - "errors" - "fmt" - "os" - "strconv" - "time" -) - -type Config struct { - Env string - HTTPAddr string - DatabaseURL string - JWKSURL string - RequestTimeout time.Duration - ShutdownTimeout time.Duration -} - -func Load() (Config, error) { - cfg := Config{ - Env: getenv("APP_ENV", "development"), - HTTPAddr: getenv("HTTP_ADDR", ":8080"), - DatabaseURL: os.Getenv("DATABASE_URL"), - JWKSURL: os.Getenv("JWKS_URL"), - RequestTimeout: getDuration("REQUEST_TIMEOUT", 10*time.Second), - ShutdownTimeout: getDuration("SHUTDOWN_TIMEOUT", 10*time.Second), - } - - if cfg.DatabaseURL == "" { - return Config{}, errors.New("DATABASE_URL is required") - } - if cfg.JWKSURL == "" { - return Config{}, errors.New("JWKS_URL is required") - } - return cfg, nil -} - -func getenv(key, fallback string) string { - if val := os.Getenv(key); val != "" { - return val - } - return fallback -} - -func getDuration(key string, fallback time.Duration) time.Duration { - if val := os.Getenv(key); val != "" { - parsed, err := time.ParseDuration(val) - if err == nil { - return parsed - } - } - return fallback -} - -func MustLoad() Config { - cfg, err := Load() - if err != nil { - panic(fmt.Errorf("config load failed: %w", err)) - } - return cfg -} - -func GetBool(key string, fallback bool) bool { - val := os.Getenv(key) - if val == "" { - return fallback - } - parsed, err := strconv.ParseBool(val) - if err != nil { - return fallback - } - return parsed -} diff --git a/crm-core/internal/db/migrations/0001_init.down.sql b/crm-core/internal/db/migrations/0001_init.down.sql deleted file mode 100644 index 7e85f25..0000000 --- a/crm-core/internal/db/migrations/0001_init.down.sql +++ /dev/null @@ -1,9 +0,0 @@ -DROP TABLE IF EXISTS entity_tags; -DROP TABLE IF EXISTS tags; -DROP TABLE IF EXISTS notes; -DROP TABLE IF EXISTS activities; -DROP TABLE IF EXISTS deals; -DROP TABLE IF EXISTS stages; -DROP TABLE IF EXISTS pipelines; -DROP TABLE IF EXISTS contacts; -DROP TABLE IF EXISTS accounts; diff --git a/crm-core/internal/db/migrations/0001_init.up.sql b/crm-core/internal/db/migrations/0001_init.up.sql deleted file mode 100644 index 4421eeb..0000000 --- a/crm-core/internal/db/migrations/0001_init.up.sql +++ /dev/null @@ -1,129 +0,0 @@ -CREATE TABLE accounts ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - name TEXT NOT NULL, - legal_name TEXT, - document TEXT, - website TEXT, - industry TEXT, - size TEXT, - status TEXT NOT NULL, - external_refs JSONB DEFAULT '{}'::jsonb, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL -); - -CREATE INDEX accounts_tenant_status_idx ON accounts (tenant_id, status); - -CREATE TABLE contacts ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - account_id UUID, - name TEXT NOT NULL, - email TEXT, - phone TEXT, - role_title TEXT, - status TEXT NOT NULL, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL -); - -CREATE INDEX contacts_tenant_account_idx ON contacts (tenant_id, account_id); -CREATE INDEX contacts_tenant_status_idx ON contacts (tenant_id, status); - -CREATE TABLE pipelines ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - name TEXT NOT NULL, - is_default BOOLEAN NOT NULL DEFAULT false, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL -); - -CREATE TABLE stages ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - pipeline_id UUID NOT NULL REFERENCES pipelines(id), - name TEXT NOT NULL, - order_index INT NOT NULL, - is_won BOOLEAN NOT NULL DEFAULT false, - is_lost BOOLEAN NOT NULL DEFAULT false, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL, - UNIQUE (tenant_id, pipeline_id, order_index) -); - -CREATE INDEX stages_tenant_pipeline_order_idx ON stages (tenant_id, pipeline_id, order_index); - -CREATE TABLE deals ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - title TEXT NOT NULL, - account_id UUID, - contact_id UUID, - pipeline_id UUID NOT NULL REFERENCES pipelines(id), - stage_id UUID NOT NULL REFERENCES stages(id), - value_cents BIGINT NOT NULL, - currency TEXT NOT NULL DEFAULT 'BRL', - status TEXT NOT NULL, - source TEXT, - expected_close_date TIMESTAMPTZ, - closed_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL -); - -CREATE INDEX deals_tenant_status_idx ON deals (tenant_id, status); -CREATE INDEX deals_tenant_stage_idx ON deals (tenant_id, stage_id); -CREATE INDEX deals_tenant_account_idx ON deals (tenant_id, account_id); - -CREATE TABLE activities ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - type TEXT NOT NULL, - deal_id UUID, - account_id UUID, - contact_id UUID, - title TEXT NOT NULL, - due_at TIMESTAMPTZ, - status TEXT NOT NULL, - assigned_user_id TEXT NOT NULL, - completed_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL -); - -CREATE INDEX activities_tenant_status_idx ON activities (tenant_id, status); - -CREATE TABLE notes ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - entity_type TEXT NOT NULL, - entity_id UUID NOT NULL, - body TEXT NOT NULL, - created_by_user_id TEXT NOT NULL, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL -); - -CREATE INDEX notes_tenant_entity_idx ON notes (tenant_id, entity_type, entity_id); - -CREATE TABLE tags ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - name TEXT NOT NULL, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL, - UNIQUE (tenant_id, name) -); - -CREATE TABLE entity_tags ( - tenant_id UUID NOT NULL, - entity_type TEXT NOT NULL, - entity_id UUID NOT NULL, - tag_id UUID NOT NULL REFERENCES tags(id), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - PRIMARY KEY (tenant_id, entity_type, entity_id, tag_id) -); - -CREATE INDEX entity_tags_tenant_entity_idx ON entity_tags (tenant_id, entity_type, entity_id); diff --git a/crm-core/internal/db/queries/accounts.sql b/crm-core/internal/db/queries/accounts.sql deleted file mode 100644 index 085fdfe..0000000 --- a/crm-core/internal/db/queries/accounts.sql +++ /dev/null @@ -1,17 +0,0 @@ --- name: CreateAccount :exec -INSERT INTO accounts (id, tenant_id, name, legal_name, document, website, industry, size, status, external_refs, created_at, updated_at) -VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12); - --- name: UpdateAccount :exec -UPDATE accounts -SET name=$1, legal_name=$2, document=$3, website=$4, industry=$5, size=$6, status=$7, external_refs=$8, updated_at=$9 -WHERE tenant_id=$10 AND id=$11; - --- name: GetAccount :one -SELECT * FROM accounts WHERE tenant_id=$1 AND id=$2; - --- name: ListAccounts :many -SELECT * FROM accounts WHERE tenant_id=$1 AND status <> 'archived' ORDER BY created_at DESC; - --- name: ArchiveAccount :exec -UPDATE accounts SET status='archived', updated_at=$1 WHERE tenant_id=$2 AND id=$3; diff --git a/crm-core/internal/db/queries/activities.sql b/crm-core/internal/db/queries/activities.sql deleted file mode 100644 index 25c481e..0000000 --- a/crm-core/internal/db/queries/activities.sql +++ /dev/null @@ -1,12 +0,0 @@ --- name: CreateActivity :exec -INSERT INTO activities (id, tenant_id, type, deal_id, account_id, contact_id, title, due_at, status, assigned_user_id, created_at, updated_at) -VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12); - --- name: ListActivities :many -SELECT * FROM activities WHERE tenant_id=$1 ORDER BY created_at DESC; - --- name: CompleteActivity :exec -UPDATE activities SET status='done', completed_at=$1, updated_at=$2 WHERE tenant_id=$3 AND id=$4; - --- name: CancelActivity :exec -UPDATE activities SET status='canceled', updated_at=$1 WHERE tenant_id=$2 AND id=$3; diff --git a/crm-core/internal/db/queries/contacts.sql b/crm-core/internal/db/queries/contacts.sql deleted file mode 100644 index bb8284a..0000000 --- a/crm-core/internal/db/queries/contacts.sql +++ /dev/null @@ -1,17 +0,0 @@ --- name: CreateContact :exec -INSERT INTO contacts (id, tenant_id, account_id, name, email, phone, role_title, status, created_at, updated_at) -VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10); - --- name: UpdateContact :exec -UPDATE contacts -SET account_id=$1, name=$2, email=$3, phone=$4, role_title=$5, status=$6, updated_at=$7 -WHERE tenant_id=$8 AND id=$9; - --- name: GetContact :one -SELECT * FROM contacts WHERE tenant_id=$1 AND id=$2; - --- name: ListContacts :many -SELECT * FROM contacts WHERE tenant_id=$1 AND status <> 'archived' ORDER BY created_at DESC; - --- name: ArchiveContact :exec -UPDATE contacts SET status='archived', updated_at=$1 WHERE tenant_id=$2 AND id=$3; diff --git a/crm-core/internal/db/queries/deals.sql b/crm-core/internal/db/queries/deals.sql deleted file mode 100644 index 63451d1..0000000 --- a/crm-core/internal/db/queries/deals.sql +++ /dev/null @@ -1,20 +0,0 @@ --- name: CreateDeal :exec -INSERT INTO deals (id, tenant_id, title, account_id, contact_id, pipeline_id, stage_id, value_cents, currency, status, source, expected_close_date, created_at, updated_at) -VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14); - --- name: UpdateDeal :exec -UPDATE deals -SET title=$1, stage_id=$2, value_cents=$3, currency=$4, source=$5, expected_close_date=$6, updated_at=$7 -WHERE tenant_id=$8 AND id=$9; - --- name: GetDeal :one -SELECT * FROM deals WHERE tenant_id=$1 AND id=$2; - --- name: ListDeals :many -SELECT * FROM deals WHERE tenant_id=$1 ORDER BY created_at DESC; - --- name: MoveDealStage :exec -UPDATE deals SET stage_id=$1, updated_at=$2 WHERE tenant_id=$3 AND id=$4; - --- name: CloseDeal :exec -UPDATE deals SET status=$1, closed_at=$2, updated_at=$3 WHERE tenant_id=$4 AND id=$5; diff --git a/crm-core/internal/db/queries/notes.sql b/crm-core/internal/db/queries/notes.sql deleted file mode 100644 index 4bdda36..0000000 --- a/crm-core/internal/db/queries/notes.sql +++ /dev/null @@ -1,6 +0,0 @@ --- name: CreateNote :exec -INSERT INTO notes (id, tenant_id, entity_type, entity_id, body, created_by_user_id, created_at, updated_at) -VALUES ($1,$2,$3,$4,$5,$6,$7,$8); - --- name: ListNotes :many -SELECT * FROM notes WHERE tenant_id=$1 ORDER BY created_at DESC; diff --git a/crm-core/internal/db/queries/stages.sql b/crm-core/internal/db/queries/stages.sql deleted file mode 100644 index 9fa7272..0000000 --- a/crm-core/internal/db/queries/stages.sql +++ /dev/null @@ -1,13 +0,0 @@ --- name: CreatePipeline :exec -INSERT INTO pipelines (id, tenant_id, name, is_default, created_at, updated_at) -VALUES ($1,$2,$3,$4,$5,$6); - --- name: ListPipelines :many -SELECT * FROM pipelines WHERE tenant_id=$1 ORDER BY created_at DESC; - --- name: CreateStage :exec -INSERT INTO stages (id, tenant_id, pipeline_id, name, order_index, is_won, is_lost, created_at, updated_at) -VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9); - --- name: ListStages :many -SELECT * FROM stages WHERE tenant_id=$1 AND pipeline_id=$2 ORDER BY order_index ASC; diff --git a/crm-core/internal/db/queries/tags.sql b/crm-core/internal/db/queries/tags.sql deleted file mode 100644 index 3a5abc2..0000000 --- a/crm-core/internal/db/queries/tags.sql +++ /dev/null @@ -1,16 +0,0 @@ --- name: UpsertTag :exec -INSERT INTO tags (id, tenant_id, name, created_at, updated_at) -VALUES ($1,$2,$3,$4,$5) -ON CONFLICT (tenant_id, name) -DO UPDATE SET updated_at=EXCLUDED.updated_at; - --- name: ListTags :many -SELECT * FROM tags WHERE tenant_id=$1 ORDER BY name ASC; - --- name: AssignTag :exec -INSERT INTO entity_tags (tenant_id, entity_type, entity_id, tag_id) -VALUES ($1,$2,$3,$4) -ON CONFLICT DO NOTHING; - --- name: UnassignTag :exec -DELETE FROM entity_tags WHERE tenant_id=$1 AND entity_type=$2 AND entity_id=$3 AND tag_id=$4; diff --git a/crm-core/internal/db/sqlc/sqlc.yaml b/crm-core/internal/db/sqlc/sqlc.yaml deleted file mode 100644 index 1542370..0000000 --- a/crm-core/internal/db/sqlc/sqlc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -version: "2" -sql: - - engine: "postgresql" - schema: "../migrations" - queries: "../queries" - gen: - go: - package: "db" - out: "../generated" - sql_package: "pgx/v5" diff --git a/crm-core/internal/domain/accounts/account.go b/crm-core/internal/domain/accounts/account.go deleted file mode 100644 index 1abc632..0000000 --- a/crm-core/internal/domain/accounts/account.go +++ /dev/null @@ -1,25 +0,0 @@ -package accounts - -import "time" - -type Status string - -const ( - StatusActive Status = "active" - StatusArchived Status = "archived" -) - -type Account struct { - ID string - TenantID string - Name string - LegalName *string - Document *string - Website *string - Industry *string - Size *string - Status Status - ExternalRefs map[string]string - CreatedAt time.Time - UpdatedAt time.Time -} diff --git a/crm-core/internal/domain/activities/activity.go b/crm-core/internal/domain/activities/activity.go deleted file mode 100644 index 0de6c1c..0000000 --- a/crm-core/internal/domain/activities/activity.go +++ /dev/null @@ -1,19 +0,0 @@ -package activities - -import "time" - -type Activity struct { - ID string - TenantID string - Type Type - DealID *string - AccountID *string - ContactID *string - Title string - DueAt *time.Time - Status Status - AssignedUserID string - CompletedAt *time.Time - CreatedAt time.Time - UpdatedAt time.Time -} diff --git a/crm-core/internal/domain/activities/status.go b/crm-core/internal/domain/activities/status.go deleted file mode 100644 index 0351971..0000000 --- a/crm-core/internal/domain/activities/status.go +++ /dev/null @@ -1,9 +0,0 @@ -package activities - -type Status string - -const ( - StatusOpen Status = "open" - StatusDone Status = "done" - StatusCanceled Status = "canceled" -) diff --git a/crm-core/internal/domain/activities/types.go b/crm-core/internal/domain/activities/types.go deleted file mode 100644 index 1d02a55..0000000 --- a/crm-core/internal/domain/activities/types.go +++ /dev/null @@ -1,12 +0,0 @@ -package activities - -type Type string - -const ( - TypeCall Type = "call" - TypeMeeting Type = "meeting" - TypeTask Type = "task" - TypeEmail Type = "email" - TypeWhatsApp Type = "whatsapp" - TypeOther Type = "other" -) diff --git a/crm-core/internal/domain/common/errors.go b/crm-core/internal/domain/common/errors.go deleted file mode 100644 index caafd05..0000000 --- a/crm-core/internal/domain/common/errors.go +++ /dev/null @@ -1,13 +0,0 @@ -package common - -import "errors" - -var ( - ErrNotFound = errors.New("not found") - ErrInvalidInput = errors.New("invalid input") - ErrUnauthorized = errors.New("unauthorized") - ErrForbidden = errors.New("forbidden") - ErrConflict = errors.New("conflict") - ErrPrecondition = errors.New("precondition failed") - ErrInternal = errors.New("internal error") -) diff --git a/crm-core/internal/domain/common/identifiers.go b/crm-core/internal/domain/common/identifiers.go deleted file mode 100644 index 7bd076a..0000000 --- a/crm-core/internal/domain/common/identifiers.go +++ /dev/null @@ -1,7 +0,0 @@ -package common - -import "github.com/google/uuid" - -func NewID() string { - return uuid.NewString() -} diff --git a/crm-core/internal/domain/common/time.go b/crm-core/internal/domain/common/time.go deleted file mode 100644 index 8220a75..0000000 --- a/crm-core/internal/domain/common/time.go +++ /dev/null @@ -1,7 +0,0 @@ -package common - -import "time" - -func NowUTC() time.Time { - return time.Now().UTC() -} diff --git a/crm-core/internal/domain/contacts/contact.go b/crm-core/internal/domain/contacts/contact.go deleted file mode 100644 index f06029c..0000000 --- a/crm-core/internal/domain/contacts/contact.go +++ /dev/null @@ -1,23 +0,0 @@ -package contacts - -import "time" - -type Status string - -const ( - StatusActive Status = "active" - StatusArchived Status = "archived" -) - -type Contact struct { - ID string - TenantID string - AccountID *string - Name string - Email *string - Phone *string - RoleTitle *string - Status Status - CreatedAt time.Time - UpdatedAt time.Time -} diff --git a/crm-core/internal/domain/deals/deal.go b/crm-core/internal/domain/deals/deal.go deleted file mode 100644 index 3478d09..0000000 --- a/crm-core/internal/domain/deals/deal.go +++ /dev/null @@ -1,20 +0,0 @@ -package deals - -import "time" - -type Deal struct { - ID string - TenantID string - Title string - AccountID *string - ContactID *string - PipelineID string - StageID string - Value Money - Status Status - Source *string - ExpectedClose *time.Time - ClosedAt *time.Time - CreatedAt time.Time - UpdatedAt time.Time -} diff --git a/crm-core/internal/domain/deals/money.go b/crm-core/internal/domain/deals/money.go deleted file mode 100644 index 73f681b..0000000 --- a/crm-core/internal/domain/deals/money.go +++ /dev/null @@ -1,6 +0,0 @@ -package deals - -type Money struct { - ValueCents int64 - Currency string -} diff --git a/crm-core/internal/domain/deals/status.go b/crm-core/internal/domain/deals/status.go deleted file mode 100644 index b6712bd..0000000 --- a/crm-core/internal/domain/deals/status.go +++ /dev/null @@ -1,9 +0,0 @@ -package deals - -type Status string - -const ( - StatusOpen Status = "open" - StatusWon Status = "won" - StatusLost Status = "lost" -) diff --git a/crm-core/internal/domain/notes/note.go b/crm-core/internal/domain/notes/note.go deleted file mode 100644 index 455923f..0000000 --- a/crm-core/internal/domain/notes/note.go +++ /dev/null @@ -1,14 +0,0 @@ -package notes - -import "time" - -type Note struct { - ID string - TenantID string - EntityType string - EntityID string - Body string - CreatedByUserID string - CreatedAt time.Time - UpdatedAt time.Time -} diff --git a/crm-core/internal/domain/pipelines/pipeline.go b/crm-core/internal/domain/pipelines/pipeline.go deleted file mode 100644 index fcbdb16..0000000 --- a/crm-core/internal/domain/pipelines/pipeline.go +++ /dev/null @@ -1,12 +0,0 @@ -package pipelines - -import "time" - -type Pipeline struct { - ID string - TenantID string - Name string - IsDefault bool - CreatedAt time.Time - UpdatedAt time.Time -} diff --git a/crm-core/internal/domain/pipelines/stage.go b/crm-core/internal/domain/pipelines/stage.go deleted file mode 100644 index db86dfc..0000000 --- a/crm-core/internal/domain/pipelines/stage.go +++ /dev/null @@ -1,15 +0,0 @@ -package pipelines - -import "time" - -type Stage struct { - ID string - TenantID string - PipelineID string - Name string - OrderIndex int32 - IsWon bool - IsLost bool - CreatedAt time.Time - UpdatedAt time.Time -} diff --git a/crm-core/internal/domain/tags/tag.go b/crm-core/internal/domain/tags/tag.go deleted file mode 100644 index 4aa161c..0000000 --- a/crm-core/internal/domain/tags/tag.go +++ /dev/null @@ -1,11 +0,0 @@ -package tags - -import "time" - -type Tag struct { - ID string - TenantID string - Name string - CreatedAt time.Time - UpdatedAt time.Time -} diff --git a/crm-core/internal/infrastructure/auth/claims.go b/crm-core/internal/infrastructure/auth/claims.go deleted file mode 100644 index 5c82cb7..0000000 --- a/crm-core/internal/infrastructure/auth/claims.go +++ /dev/null @@ -1,9 +0,0 @@ -package auth - -import "github.com/golang-jwt/jwt/v5" - -type Claims struct { - TenantID string `json:"tenantId"` - Roles []string `json:"roles"` - jwt.RegisteredClaims -} diff --git a/crm-core/internal/infrastructure/auth/jwt_middleware.go b/crm-core/internal/infrastructure/auth/jwt_middleware.go deleted file mode 100644 index 4424b98..0000000 --- a/crm-core/internal/infrastructure/auth/jwt_middleware.go +++ /dev/null @@ -1,117 +0,0 @@ -package auth - -import ( - "context" - "errors" - "net/http" - "strings" - "time" - - "github.com/MicahParks/keyfunc/v2" - "github.com/golang-jwt/jwt/v5" -) - -type ContextKey string - -const ( - ContextTenantID ContextKey = "tenant_id" - ContextUserID ContextKey = "user_id" - ContextRoles ContextKey = "roles" -) - -type Middleware struct { - jwks *keyfunc.JWKS -} - -func NewMiddleware(jwksURL string) (*Middleware, error) { - jwks, err := keyfunc.Get(jwksURL, keyfunc.Options{RefreshInterval: time.Hour}) - if err != nil { - return nil, err - } - return &Middleware{jwks: jwks}, nil -} - -func (m *Middleware) RequireJWT(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - claims := &Claims{} - token, err := parseToken(r, m.jwks.Keyfunc, claims) - if err != nil || !token.Valid { - http.Error(w, "unauthorized", http.StatusUnauthorized) - return - } - if claims.Subject == "" || claims.TenantID == "" { - http.Error(w, "invalid token claims", http.StatusUnauthorized) - return - } - ctx := context.WithValue(r.Context(), ContextTenantID, claims.TenantID) - ctx = context.WithValue(ctx, ContextUserID, claims.Subject) - ctx = context.WithValue(ctx, ContextRoles, claims.Roles) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -func RequireScopes(scopes ...string) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - roles, ok := r.Context().Value(ContextRoles).([]string) - if !ok { - http.Error(w, "forbidden", http.StatusForbidden) - return - } - if !hasScopes(roles, scopes) { - http.Error(w, "forbidden", http.StatusForbidden) - return - } - next.ServeHTTP(w, r) - }) - } -} - -func TenantIDFromContext(ctx context.Context) (string, error) { - val := ctx.Value(ContextTenantID) - if val == nil { - return "", errors.New("tenant id not found") - } - tenantID, ok := val.(string) - if !ok || tenantID == "" { - return "", errors.New("tenant id invalid") - } - return tenantID, nil -} - -func UserIDFromContext(ctx context.Context) (string, error) { - val := ctx.Value(ContextUserID) - if val == nil { - return "", errors.New("user id not found") - } - userID, ok := val.(string) - if !ok || userID == "" { - return "", errors.New("user id invalid") - } - return userID, nil -} - -func parseToken(r *http.Request, keyfunc jwt.Keyfunc, claims *Claims) (*jwt.Token, error) { - auth := r.Header.Get("Authorization") - if auth == "" { - return nil, errors.New("missing authorization header") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { - return nil, errors.New("invalid authorization header") - } - return jwt.ParseWithClaims(parts[1], claims, keyfunc) -} - -func hasScopes(roles []string, required []string) bool { - set := make(map[string]struct{}, len(roles)) - for _, role := range roles { - set[role] = struct{}{} - } - for _, scope := range required { - if _, ok := set[scope]; ok { - return true - } - } - return false -} diff --git a/crm-core/internal/infrastructure/postgres/pool.go b/crm-core/internal/infrastructure/postgres/pool.go deleted file mode 100644 index 99c1c9b..0000000 --- a/crm-core/internal/infrastructure/postgres/pool.go +++ /dev/null @@ -1,20 +0,0 @@ -package postgres - -import ( - "context" - "time" - - "github.com/jackc/pgx/v5/pgxpool" -) - -func NewPool(ctx context.Context, databaseURL string) (*pgxpool.Pool, error) { - cfg, err := pgxpool.ParseConfig(databaseURL) - if err != nil { - return nil, err - } - cfg.MaxConns = 10 - cfg.MinConns = 2 - cfg.MaxConnLifetime = time.Hour - cfg.MaxConnIdleTime = 10 * time.Minute - return pgxpool.NewWithConfig(ctx, cfg) -} diff --git a/crm-core/internal/infrastructure/repositories/accounts_repo_sqlc.go b/crm-core/internal/infrastructure/repositories/accounts_repo_sqlc.go deleted file mode 100644 index d2365e0..0000000 --- a/crm-core/internal/infrastructure/repositories/accounts_repo_sqlc.go +++ /dev/null @@ -1,94 +0,0 @@ -package repositories - -import ( - "context" - "encoding/json" - "time" - - "crm-core/internal/domain/accounts" - "crm-core/internal/domain/common" - - "github.com/jackc/pgx/v5/pgxpool" -) - -type AccountsRepository struct { - pool *pgxpool.Pool -} - -func NewAccountsRepository(pool *pgxpool.Pool) *AccountsRepository { - return &AccountsRepository{pool: pool} -} - -func (r *AccountsRepository) Create(ctx context.Context, account accounts.Account) (accounts.Account, error) { - now := common.NowUTC() - account.CreatedAt = now - account.UpdatedAt = now - externalRefs, _ := json.Marshal(account.ExternalRefs) - _, err := r.pool.Exec(ctx, ` - INSERT INTO accounts (id, tenant_id, name, legal_name, document, website, industry, size, status, external_refs, created_at, updated_at) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) - `, account.ID, account.TenantID, account.Name, account.LegalName, account.Document, account.Website, account.Industry, account.Size, account.Status, externalRefs, account.CreatedAt, account.UpdatedAt) - return account, err -} - -func (r *AccountsRepository) Update(ctx context.Context, account accounts.Account) (accounts.Account, error) { - account.UpdatedAt = common.NowUTC() - externalRefs, _ := json.Marshal(account.ExternalRefs) - _, err := r.pool.Exec(ctx, ` - UPDATE accounts - SET name=$1, legal_name=$2, document=$3, website=$4, industry=$5, size=$6, status=$7, external_refs=$8, updated_at=$9 - WHERE tenant_id=$10 AND id=$11 - `, account.Name, account.LegalName, account.Document, account.Website, account.Industry, account.Size, account.Status, externalRefs, account.UpdatedAt, account.TenantID, account.ID) - return account, err -} - -func (r *AccountsRepository) List(ctx context.Context, tenantID string) ([]accounts.Account, error) { - rows, err := r.pool.Query(ctx, ` - SELECT id, tenant_id, name, legal_name, document, website, industry, size, status, external_refs, created_at, updated_at - FROM accounts - WHERE tenant_id=$1 AND status <> 'archived' - ORDER BY created_at DESC - `, tenantID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []accounts.Account - for rows.Next() { - var account accounts.Account - var externalRefsBytes []byte - if err := rows.Scan(&account.ID, &account.TenantID, &account.Name, &account.LegalName, &account.Document, &account.Website, &account.Industry, &account.Size, &account.Status, &externalRefsBytes, &account.CreatedAt, &account.UpdatedAt); err != nil { - return nil, err - } - if len(externalRefsBytes) > 0 { - _ = json.Unmarshal(externalRefsBytes, &account.ExternalRefs) - } - items = append(items, account) - } - return items, rows.Err() -} - -func (r *AccountsRepository) Get(ctx context.Context, tenantID, id string) (accounts.Account, error) { - var account accounts.Account - var externalRefsBytes []byte - row := r.pool.QueryRow(ctx, ` - SELECT id, tenant_id, name, legal_name, document, website, industry, size, status, external_refs, created_at, updated_at - FROM accounts - WHERE tenant_id=$1 AND id=$2 - `, tenantID, id) - if err := row.Scan(&account.ID, &account.TenantID, &account.Name, &account.LegalName, &account.Document, &account.Website, &account.Industry, &account.Size, &account.Status, &externalRefsBytes, &account.CreatedAt, &account.UpdatedAt); err != nil { - return accounts.Account{}, err - } - if len(externalRefsBytes) > 0 { - _ = json.Unmarshal(externalRefsBytes, &account.ExternalRefs) - } - return account, nil -} - -func (r *AccountsRepository) Archive(ctx context.Context, tenantID, id string) error { - _, err := r.pool.Exec(ctx, ` - UPDATE accounts SET status='archived', updated_at=$1 - WHERE tenant_id=$2 AND id=$3 - `, time.Now().UTC(), tenantID, id) - return err -} diff --git a/crm-core/internal/infrastructure/repositories/activities_repo_sqlc.go b/crm-core/internal/infrastructure/repositories/activities_repo_sqlc.go deleted file mode 100644 index 615b7e5..0000000 --- a/crm-core/internal/infrastructure/repositories/activities_repo_sqlc.go +++ /dev/null @@ -1,107 +0,0 @@ -package repositories - -import ( - "context" - "fmt" - "strings" - - "crm-core/internal/application/activities" - domain "crm-core/internal/domain/activities" - "crm-core/internal/domain/common" - - "github.com/jackc/pgx/v5/pgxpool" -) - -type ActivitiesRepository struct { - pool *pgxpool.Pool -} - -func NewActivitiesRepository(pool *pgxpool.Pool) *ActivitiesRepository { - return &ActivitiesRepository{pool: pool} -} - -func (r *ActivitiesRepository) Create(ctx context.Context, activity domain.Activity) (domain.Activity, error) { - now := common.NowUTC() - activity.CreatedAt = now - activity.UpdatedAt = now - _, err := r.pool.Exec(ctx, ` - INSERT INTO activities (id, tenant_id, type, deal_id, account_id, contact_id, title, due_at, status, assigned_user_id, created_at, updated_at) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) - `, activity.ID, activity.TenantID, activity.Type, activity.DealID, activity.AccountID, activity.ContactID, activity.Title, activity.DueAt, activity.Status, activity.AssignedUserID, activity.CreatedAt, activity.UpdatedAt) - return activity, err -} - -func (r *ActivitiesRepository) List(ctx context.Context, tenantID string, filters activities.ListFilters) ([]domain.Activity, error) { - clauses := []string{"tenant_id=$1"} - args := []any{tenantID} - idx := 2 - if filters.Status != nil { - clauses = append(clauses, fmt.Sprintf("status=$%d", idx)) - args = append(args, *filters.Status) - idx++ - } - if filters.DealID != nil { - clauses = append(clauses, fmt.Sprintf("deal_id=$%d", idx)) - args = append(args, *filters.DealID) - idx++ - } - if filters.AssignedUserID != nil { - clauses = append(clauses, fmt.Sprintf("assigned_user_id=$%d", idx)) - args = append(args, *filters.AssignedUserID) - idx++ - } - where := strings.Join(clauses, " AND ") - query := fmt.Sprintf(` - SELECT id, tenant_id, type, deal_id, account_id, contact_id, title, due_at, status, assigned_user_id, completed_at, created_at, updated_at - FROM activities - WHERE %s - ORDER BY created_at DESC - `, where) - rows, err := r.pool.Query(ctx, query, args...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []domain.Activity - for rows.Next() { - var activity domain.Activity - if err := rows.Scan(&activity.ID, &activity.TenantID, &activity.Type, &activity.DealID, &activity.AccountID, &activity.ContactID, &activity.Title, &activity.DueAt, &activity.Status, &activity.AssignedUserID, &activity.CompletedAt, &activity.CreatedAt, &activity.UpdatedAt); err != nil { - return nil, err - } - items = append(items, activity) - } - return items, rows.Err() -} - -func (r *ActivitiesRepository) Complete(ctx context.Context, tenantID, id string) (domain.Activity, error) { - _, err := r.pool.Exec(ctx, ` - UPDATE activities - SET status='done', completed_at=COALESCE(completed_at, $1), updated_at=$2 - WHERE tenant_id=$3 AND id=$4 - `, common.NowUTC(), common.NowUTC(), tenantID, id) - if err != nil { - return domain.Activity{}, err - } - return r.get(ctx, tenantID, id) -} - -func (r *ActivitiesRepository) Cancel(ctx context.Context, tenantID, id string) error { - _, err := r.pool.Exec(ctx, ` - UPDATE activities SET status='canceled', updated_at=$1 - WHERE tenant_id=$2 AND id=$3 - `, common.NowUTC(), tenantID, id) - return err -} - -func (r *ActivitiesRepository) get(ctx context.Context, tenantID, id string) (domain.Activity, error) { - var activity domain.Activity - row := r.pool.QueryRow(ctx, ` - SELECT id, tenant_id, type, deal_id, account_id, contact_id, title, due_at, status, assigned_user_id, completed_at, created_at, updated_at - FROM activities - WHERE tenant_id=$1 AND id=$2 - `, tenantID, id) - if err := row.Scan(&activity.ID, &activity.TenantID, &activity.Type, &activity.DealID, &activity.AccountID, &activity.ContactID, &activity.Title, &activity.DueAt, &activity.Status, &activity.AssignedUserID, &activity.CompletedAt, &activity.CreatedAt, &activity.UpdatedAt); err != nil { - return domain.Activity{}, err - } - return activity, nil -} diff --git a/crm-core/internal/infrastructure/repositories/contacts_repo_sqlc.go b/crm-core/internal/infrastructure/repositories/contacts_repo_sqlc.go deleted file mode 100644 index c9eaa2c..0000000 --- a/crm-core/internal/infrastructure/repositories/contacts_repo_sqlc.go +++ /dev/null @@ -1,83 +0,0 @@ -package repositories - -import ( - "context" - "time" - - "crm-core/internal/domain/common" - "crm-core/internal/domain/contacts" - - "github.com/jackc/pgx/v5/pgxpool" -) - -type ContactsRepository struct { - pool *pgxpool.Pool -} - -func NewContactsRepository(pool *pgxpool.Pool) *ContactsRepository { - return &ContactsRepository{pool: pool} -} - -func (r *ContactsRepository) Create(ctx context.Context, contact contacts.Contact) (contacts.Contact, error) { - now := common.NowUTC() - contact.CreatedAt = now - contact.UpdatedAt = now - _, err := r.pool.Exec(ctx, ` - INSERT INTO contacts (id, tenant_id, account_id, name, email, phone, role_title, status, created_at, updated_at) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) - `, contact.ID, contact.TenantID, contact.AccountID, contact.Name, contact.Email, contact.Phone, contact.RoleTitle, contact.Status, contact.CreatedAt, contact.UpdatedAt) - return contact, err -} - -func (r *ContactsRepository) Update(ctx context.Context, contact contacts.Contact) (contacts.Contact, error) { - contact.UpdatedAt = common.NowUTC() - _, err := r.pool.Exec(ctx, ` - UPDATE contacts - SET account_id=$1, name=$2, email=$3, phone=$4, role_title=$5, status=$6, updated_at=$7 - WHERE tenant_id=$8 AND id=$9 - `, contact.AccountID, contact.Name, contact.Email, contact.Phone, contact.RoleTitle, contact.Status, contact.UpdatedAt, contact.TenantID, contact.ID) - return contact, err -} - -func (r *ContactsRepository) List(ctx context.Context, tenantID string) ([]contacts.Contact, error) { - rows, err := r.pool.Query(ctx, ` - SELECT id, tenant_id, account_id, name, email, phone, role_title, status, created_at, updated_at - FROM contacts - WHERE tenant_id=$1 AND status <> 'archived' - ORDER BY created_at DESC - `, tenantID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []contacts.Contact - for rows.Next() { - var contact contacts.Contact - if err := rows.Scan(&contact.ID, &contact.TenantID, &contact.AccountID, &contact.Name, &contact.Email, &contact.Phone, &contact.RoleTitle, &contact.Status, &contact.CreatedAt, &contact.UpdatedAt); err != nil { - return nil, err - } - items = append(items, contact) - } - return items, rows.Err() -} - -func (r *ContactsRepository) Get(ctx context.Context, tenantID, id string) (contacts.Contact, error) { - var contact contacts.Contact - row := r.pool.QueryRow(ctx, ` - SELECT id, tenant_id, account_id, name, email, phone, role_title, status, created_at, updated_at - FROM contacts - WHERE tenant_id=$1 AND id=$2 - `, tenantID, id) - if err := row.Scan(&contact.ID, &contact.TenantID, &contact.AccountID, &contact.Name, &contact.Email, &contact.Phone, &contact.RoleTitle, &contact.Status, &contact.CreatedAt, &contact.UpdatedAt); err != nil { - return contacts.Contact{}, err - } - return contact, nil -} - -func (r *ContactsRepository) Archive(ctx context.Context, tenantID, id string) error { - _, err := r.pool.Exec(ctx, ` - UPDATE contacts SET status='archived', updated_at=$1 - WHERE tenant_id=$2 AND id=$3 - `, time.Now().UTC(), tenantID, id) - return err -} diff --git a/crm-core/internal/infrastructure/repositories/deals_repo_sqlc.go b/crm-core/internal/infrastructure/repositories/deals_repo_sqlc.go deleted file mode 100644 index ca88762..0000000 --- a/crm-core/internal/infrastructure/repositories/deals_repo_sqlc.go +++ /dev/null @@ -1,119 +0,0 @@ -package repositories - -import ( - "context" - "fmt" - "strings" - - "crm-core/internal/application/deals" - domain "crm-core/internal/domain/deals" - "crm-core/internal/domain/common" - - "github.com/jackc/pgx/v5/pgxpool" -) - -type DealsRepository struct { - pool *pgxpool.Pool -} - -func NewDealsRepository(pool *pgxpool.Pool) *DealsRepository { - return &DealsRepository{pool: pool} -} - -func (r *DealsRepository) Create(ctx context.Context, deal domain.Deal) (domain.Deal, error) { - now := common.NowUTC() - deal.CreatedAt = now - deal.UpdatedAt = now - _, err := r.pool.Exec(ctx, ` - INSERT INTO deals (id, tenant_id, title, account_id, contact_id, pipeline_id, stage_id, value_cents, currency, status, source, expected_close_date, created_at, updated_at) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) - `, deal.ID, deal.TenantID, deal.Title, deal.AccountID, deal.ContactID, deal.PipelineID, deal.StageID, deal.Value.ValueCents, deal.Value.Currency, deal.Status, deal.Source, deal.ExpectedClose, deal.CreatedAt, deal.UpdatedAt) - return deal, err -} - -func (r *DealsRepository) Update(ctx context.Context, deal domain.Deal) (domain.Deal, error) { - deal.UpdatedAt = common.NowUTC() - _, err := r.pool.Exec(ctx, ` - UPDATE deals - SET title=$1, stage_id=$2, value_cents=$3, currency=$4, source=$5, expected_close_date=$6, updated_at=$7 - WHERE tenant_id=$8 AND id=$9 - `, deal.Title, deal.StageID, deal.Value.ValueCents, deal.Value.Currency, deal.Source, deal.ExpectedClose, deal.UpdatedAt, deal.TenantID, deal.ID) - return deal, err -} - -func (r *DealsRepository) Get(ctx context.Context, tenantID, id string) (domain.Deal, error) { - var deal domain.Deal - row := r.pool.QueryRow(ctx, ` - SELECT id, tenant_id, title, account_id, contact_id, pipeline_id, stage_id, value_cents, currency, status, source, expected_close_date, closed_at, created_at, updated_at - FROM deals - WHERE tenant_id=$1 AND id=$2 - `, tenantID, id) - if err := row.Scan(&deal.ID, &deal.TenantID, &deal.Title, &deal.AccountID, &deal.ContactID, &deal.PipelineID, &deal.StageID, &deal.Value.ValueCents, &deal.Value.Currency, &deal.Status, &deal.Source, &deal.ExpectedClose, &deal.ClosedAt, &deal.CreatedAt, &deal.UpdatedAt); err != nil { - return domain.Deal{}, err - } - return deal, nil -} - -func (r *DealsRepository) List(ctx context.Context, tenantID string, filters deals.ListFilters) ([]domain.Deal, error) { - clauses := []string{"tenant_id=$1"} - args := []any{tenantID} - idx := 2 - if filters.Status != nil { - clauses = append(clauses, fmt.Sprintf("status=$%d", idx)) - args = append(args, *filters.Status) - idx++ - } - if filters.StageID != nil { - clauses = append(clauses, fmt.Sprintf("stage_id=$%d", idx)) - args = append(args, *filters.StageID) - idx++ - } - if filters.AccountID != nil { - clauses = append(clauses, fmt.Sprintf("account_id=$%d", idx)) - args = append(args, *filters.AccountID) - idx++ - } - where := strings.Join(clauses, " AND ") - query := fmt.Sprintf(` - SELECT id, tenant_id, title, account_id, contact_id, pipeline_id, stage_id, value_cents, currency, status, source, expected_close_date, closed_at, created_at, updated_at - FROM deals - WHERE %s - ORDER BY created_at DESC - `, where) - rows, err := r.pool.Query(ctx, query, args...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []domain.Deal - for rows.Next() { - var deal domain.Deal - if err := rows.Scan(&deal.ID, &deal.TenantID, &deal.Title, &deal.AccountID, &deal.ContactID, &deal.PipelineID, &deal.StageID, &deal.Value.ValueCents, &deal.Value.Currency, &deal.Status, &deal.Source, &deal.ExpectedClose, &deal.ClosedAt, &deal.CreatedAt, &deal.UpdatedAt); err != nil { - return nil, err - } - items = append(items, deal) - } - return items, rows.Err() -} - -func (r *DealsRepository) MoveStage(ctx context.Context, tenantID, id, stageID string) (domain.Deal, error) { - _, err := r.pool.Exec(ctx, ` - UPDATE deals SET stage_id=$1, updated_at=$2 - WHERE tenant_id=$3 AND id=$4 - `, stageID, common.NowUTC(), tenantID, id) - if err != nil { - return domain.Deal{}, err - } - return r.Get(ctx, tenantID, id) -} - -func (r *DealsRepository) Close(ctx context.Context, tenantID, id string, status domain.Status) (domain.Deal, error) { - _, err := r.pool.Exec(ctx, ` - UPDATE deals SET status=$1, closed_at=$2, updated_at=$3 - WHERE tenant_id=$4 AND id=$5 - `, status, common.NowUTC(), common.NowUTC(), tenantID, id) - if err != nil { - return domain.Deal{}, err - } - return r.Get(ctx, tenantID, id) -} diff --git a/crm-core/internal/infrastructure/repositories/notes_repo_sqlc.go b/crm-core/internal/infrastructure/repositories/notes_repo_sqlc.go deleted file mode 100644 index 6a9e918..0000000 --- a/crm-core/internal/infrastructure/repositories/notes_repo_sqlc.go +++ /dev/null @@ -1,64 +0,0 @@ -package repositories - -import ( - "context" - "fmt" - - "crm-core/internal/domain/common" - "crm-core/internal/domain/notes" - - "github.com/jackc/pgx/v5/pgxpool" -) - -type NotesRepository struct { - pool *pgxpool.Pool -} - -func NewNotesRepository(pool *pgxpool.Pool) *NotesRepository { - return &NotesRepository{pool: pool} -} - -func (r *NotesRepository) Create(ctx context.Context, note notes.Note) (notes.Note, error) { - now := common.NowUTC() - note.CreatedAt = now - note.UpdatedAt = now - _, err := r.pool.Exec(ctx, ` - INSERT INTO notes (id, tenant_id, entity_type, entity_id, body, created_by_user_id, created_at, updated_at) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8) - `, note.ID, note.TenantID, note.EntityType, note.EntityID, note.Body, note.CreatedByUserID, note.CreatedAt, note.UpdatedAt) - return note, err -} - -func (r *NotesRepository) List(ctx context.Context, tenantID string, entityType *string, entityID *string) ([]notes.Note, error) { - query := ` - SELECT id, tenant_id, entity_type, entity_id, body, created_by_user_id, created_at, updated_at - FROM notes - WHERE tenant_id=$1` - args := []any{tenantID} - idx := 2 - if entityType != nil { - query += fmt.Sprintf(" AND entity_type=$%d", idx) - args = append(args, *entityType) - idx++ - } - if entityID != nil { - query += fmt.Sprintf(" AND entity_id=$%d", idx) - args = append(args, *entityID) - idx++ - } - query += " ORDER BY created_at DESC" - rows, err := r.pool.Query(ctx, query, args...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []notes.Note - for rows.Next() { - var note notes.Note - if err := rows.Scan(¬e.ID, ¬e.TenantID, ¬e.EntityType, ¬e.EntityID, ¬e.Body, ¬e.CreatedByUserID, ¬e.CreatedAt, ¬e.UpdatedAt); err != nil { - return nil, err - } - items = append(items, note) - } - return items, rows.Err() -} diff --git a/crm-core/internal/infrastructure/repositories/stages_repo_sqlc.go b/crm-core/internal/infrastructure/repositories/stages_repo_sqlc.go deleted file mode 100644 index a156e3e..0000000 --- a/crm-core/internal/infrastructure/repositories/stages_repo_sqlc.go +++ /dev/null @@ -1,84 +0,0 @@ -package repositories - -import ( - "context" - - "crm-core/internal/domain/common" - "crm-core/internal/domain/pipelines" - - "github.com/jackc/pgx/v5/pgxpool" -) - -type StagesRepository struct { - pool *pgxpool.Pool -} - -func NewStagesRepository(pool *pgxpool.Pool) *StagesRepository { - return &StagesRepository{pool: pool} -} - -func (r *StagesRepository) CreatePipeline(ctx context.Context, pipeline pipelines.Pipeline) (pipelines.Pipeline, error) { - now := common.NowUTC() - pipeline.CreatedAt = now - pipeline.UpdatedAt = now - _, err := r.pool.Exec(ctx, ` - INSERT INTO pipelines (id, tenant_id, name, is_default, created_at, updated_at) - VALUES ($1,$2,$3,$4,$5,$6) - `, pipeline.ID, pipeline.TenantID, pipeline.Name, pipeline.IsDefault, pipeline.CreatedAt, pipeline.UpdatedAt) - return pipeline, err -} - -func (r *StagesRepository) ListPipelines(ctx context.Context, tenantID string) ([]pipelines.Pipeline, error) { - rows, err := r.pool.Query(ctx, ` - SELECT id, tenant_id, name, is_default, created_at, updated_at - FROM pipelines - WHERE tenant_id=$1 - ORDER BY created_at DESC - `, tenantID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []pipelines.Pipeline - for rows.Next() { - var pipeline pipelines.Pipeline - if err := rows.Scan(&pipeline.ID, &pipeline.TenantID, &pipeline.Name, &pipeline.IsDefault, &pipeline.CreatedAt, &pipeline.UpdatedAt); err != nil { - return nil, err - } - items = append(items, pipeline) - } - return items, rows.Err() -} - -func (r *StagesRepository) CreateStage(ctx context.Context, stage pipelines.Stage) (pipelines.Stage, error) { - now := common.NowUTC() - stage.CreatedAt = now - stage.UpdatedAt = now - _, err := r.pool.Exec(ctx, ` - INSERT INTO stages (id, tenant_id, pipeline_id, name, order_index, is_won, is_lost, created_at, updated_at) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) - `, stage.ID, stage.TenantID, stage.PipelineID, stage.Name, stage.OrderIndex, stage.IsWon, stage.IsLost, stage.CreatedAt, stage.UpdatedAt) - return stage, err -} - -func (r *StagesRepository) ListStages(ctx context.Context, tenantID, pipelineID string) ([]pipelines.Stage, error) { - rows, err := r.pool.Query(ctx, ` - SELECT id, tenant_id, pipeline_id, name, order_index, is_won, is_lost, created_at, updated_at - FROM stages - WHERE tenant_id=$1 AND pipeline_id=$2 - ORDER BY order_index ASC - `, tenantID, pipelineID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []pipelines.Stage - for rows.Next() { - var stage pipelines.Stage - if err := rows.Scan(&stage.ID, &stage.TenantID, &stage.PipelineID, &stage.Name, &stage.OrderIndex, &stage.IsWon, &stage.IsLost, &stage.CreatedAt, &stage.UpdatedAt); err != nil { - return nil, err - } - items = append(items, stage) - } - return items, rows.Err() -} diff --git a/crm-core/internal/infrastructure/repositories/tags_repo_sqlc.go b/crm-core/internal/infrastructure/repositories/tags_repo_sqlc.go deleted file mode 100644 index 2324355..0000000 --- a/crm-core/internal/infrastructure/repositories/tags_repo_sqlc.go +++ /dev/null @@ -1,70 +0,0 @@ -package repositories - -import ( - "context" - - "crm-core/internal/domain/common" - "crm-core/internal/domain/tags" - - "github.com/jackc/pgx/v5/pgxpool" -) - -type TagsRepository struct { - pool *pgxpool.Pool -} - -func NewTagsRepository(pool *pgxpool.Pool) *TagsRepository { - return &TagsRepository{pool: pool} -} - -func (r *TagsRepository) Upsert(ctx context.Context, tag tags.Tag) (tags.Tag, error) { - now := common.NowUTC() - tag.CreatedAt = now - tag.UpdatedAt = now - _, err := r.pool.Exec(ctx, ` - INSERT INTO tags (id, tenant_id, name, created_at, updated_at) - VALUES ($1,$2,$3,$4,$5) - ON CONFLICT (tenant_id, name) - DO UPDATE SET updated_at=EXCLUDED.updated_at - `, tag.ID, tag.TenantID, tag.Name, tag.CreatedAt, tag.UpdatedAt) - return tag, err -} - -func (r *TagsRepository) List(ctx context.Context, tenantID string) ([]tags.Tag, error) { - rows, err := r.pool.Query(ctx, ` - SELECT id, tenant_id, name, created_at, updated_at - FROM tags - WHERE tenant_id=$1 - ORDER BY name ASC - `, tenantID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []tags.Tag - for rows.Next() { - var tag tags.Tag - if err := rows.Scan(&tag.ID, &tag.TenantID, &tag.Name, &tag.CreatedAt, &tag.UpdatedAt); err != nil { - return nil, err - } - items = append(items, tag) - } - return items, rows.Err() -} - -func (r *TagsRepository) Assign(ctx context.Context, tenantID, entityType, entityID, tagID string) error { - _, err := r.pool.Exec(ctx, ` - INSERT INTO entity_tags (tenant_id, entity_type, entity_id, tag_id) - VALUES ($1,$2,$3,$4) - ON CONFLICT DO NOTHING - `, tenantID, entityType, entityID, tagID) - return err -} - -func (r *TagsRepository) Unassign(ctx context.Context, tenantID, entityType, entityID, tagID string) error { - _, err := r.pool.Exec(ctx, ` - DELETE FROM entity_tags - WHERE tenant_id=$1 AND entity_type=$2 AND entity_id=$3 AND tag_id=$4 - `, tenantID, entityType, entityID, tagID) - return err -} diff --git a/crm-core/internal/observability/logging.go b/crm-core/internal/observability/logging.go deleted file mode 100644 index fc1ff40..0000000 --- a/crm-core/internal/observability/logging.go +++ /dev/null @@ -1,14 +0,0 @@ -package observability - -import ( - "log/slog" - "os" -) - -func NewLogger(env string) *slog.Logger { - level := slog.LevelInfo - if env == "development" { - level = slog.LevelDebug - } - return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level})) -} diff --git a/crm-core/internal/observability/metrics.go b/crm-core/internal/observability/metrics.go deleted file mode 100644 index 49b47f5..0000000 --- a/crm-core/internal/observability/metrics.go +++ /dev/null @@ -1,3 +0,0 @@ -package observability - -// Placeholder for future metrics setup. diff --git a/dashboard/.dockerignore b/dashboard/.dockerignore deleted file mode 100644 index 3114161..0000000 --- a/dashboard/.dockerignore +++ /dev/null @@ -1,10 +0,0 @@ -node_modules -dist -.git -.env -.gitignore -Dockerfile -README.md -DASHBOARD.md -*.log -coverage diff --git a/dashboard/.eslintrc.cjs b/dashboard/.eslintrc.cjs deleted file mode 100644 index d6c9537..0000000 --- a/dashboard/.eslintrc.cjs +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, -} diff --git a/dashboard/.gitignore b/dashboard/.gitignore deleted file mode 100644 index 2033061..0000000 --- a/dashboard/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules -dist -.env -*.log -.DS_Store -coverage diff --git a/dashboard/DASHBOARD.md b/dashboard/DASHBOARD.md deleted file mode 100644 index 56f113d..0000000 --- a/dashboard/DASHBOARD.md +++ /dev/null @@ -1,71 +0,0 @@ -# DASHBOARD - -O `dashboard` é a interface de administração e operação da plataforma, construído como uma Single Page Application (SPA) moderna. Ele oferece controle centralizado sobre infraestrutura, projetos, finanças e integrações. - -## 📋 Visão Geral - -Com um design inspirado no VSCode ("VSCode-like"), o dashboard foca em densidade de informação e usabilidade para desenvolvedores e operadores. - -### Arquitetura Frontend - -```mermaid -graph TD - User[Operador] -->|Browser| SPA[React SPA] - SPA -->|Auth/Data| BaaS[Appwrite / BaaS Control Plane] - SPA -->|Git Ops| GitHub[GitHub API] - SPA -->|Infra| CF[Cloudflare API] - - subgraph Modules - SPA --> Accounts[Gerenciador de Contas] - SPA --> Kanban[Gestão de Projetos] - SPA --> Finance[Financeiro ERP] - SPA --> Terminals[Logs Realtime] - end -``` - -## 🚀 Estrutura do Projeto - -O projeto utiliza **Vite + React** para alta performance de desenvlvimento e build: - -| Diretório | Descrição | -| :--- | :--- | -| `src/pages` | Telas principais (Overview, Projects, Kanban, etc.). | -| `src/components` | Componentes reutilizáveis (Cards, Inputs, UserDropdown). | -| `src/contexts` | Gestão de estado global (Auth). | -| `src/lib` | Configurações de serviços externos (Appwrite SDK). | - -## 🛠️ Tecnologias e Otimizações - -- **Frontend**: React 18, TypeScript, TailwindCSS. -- **Build Tool**: Vite. -- **Deploy**: Docker (Nginx Alpine). -- **Integrações**: Appwrite, GitHub, Cloudflare. - -## 💻 Como Executar - -### Docker (Produção) - -```bash -docker-compose up --build -``` -O dashboard estará acessível na porta `80` (ou conforme mapeamento no compose). - -### Desenvolvimento Local - -1. **Instale dependências**: - ```bash - npm install - ``` -2. **Configuração**: - Crie o arquivo `.env` com as chaves do Appwrite (ver `.env.example`). -3. **Rodar**: - ```bash - npm run dev - ``` - -## 🔧 Detalhes do Dockerfile - -O `Dockerfile` utiliza multi-stage build para servir apenas arquivos estáticos: - -- **Builder**: Node.js 20. Compila o React para HTML/CSS/JS otimizado. -- **Runtime**: Nginx Alpine. Serve a pasta `dist` gerada, com configuração leve e performática. diff --git a/dashboard/Dockerfile b/dashboard/Dockerfile deleted file mode 100644 index 2f97528..0000000 --- a/dashboard/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -# Dockerfile -# Stage 1: Build the React application -FROM node:20-alpine AS builder - -WORKDIR /app - -COPY package.json package-lock.json ./ -RUN npm ci - -COPY . . -RUN npm run build - -# Stage 2: Serve with Distroless Node.js -FROM gcr.io/distroless/nodejs20-debian12 - -WORKDIR /app - -COPY --from=builder /app/dist ./dist -COPY --from=builder /app/server.js ./server.js -COPY --from=builder /app/node_modules ./node_modules - -EXPOSE 3000 - -CMD ["server.js"] diff --git a/dashboard/index.html b/dashboard/index.html deleted file mode 100644 index e4b78ea..0000000 --- a/dashboard/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Vite + React + TS - - -
- - - diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json deleted file mode 100644 index af857ea..0000000 --- a/dashboard/package-lock.json +++ /dev/null @@ -1,4313 +0,0 @@ -{ - "name": "dashboard", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "dashboard", - "version": "0.0.0", - "dependencies": { - "appwrite": "^21.5.0", - "lucide-react": "^0.560.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^7.10.1" - }, - "devDependencies": { - "@types/react": "^18.2.55", - "@types/react-dom": "^18.2.19", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", - "@vitejs/plugin-react": "^5.1.2", - "autoprefixer": "^10.4.22", - "eslint": "^8.56.0", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.5", - "postcss": "^8.5.6", - "tailwindcss": "^3.4.17", - "typescript": "^5.2.2", - "vite": "^7.3.1" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", - "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^18.0.0" - } - }, - "node_modules/@types/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@vitejs/plugin-react": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", - "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.5", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.53", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.18.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/appwrite": { - "version": "21.5.0", - "resolved": "https://registry.npmjs.org/appwrite/-/appwrite-21.5.0.tgz", - "integrity": "sha512-643bMRZVYXMluXvSXbdaLAi9qqTJLWbVGguKH4vH6IdKHur6gGIirhCOqAEt33pV4TOFJ55VBu8c/+Ft1ke2SA==", - "license": "BSD-3-Clause" - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", - "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.27.0", - "caniuse-lite": "^1.0.30001754", - "fraction.js": "^5.3.4", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz", - "integrity": "sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001760", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", - "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, - "license": "MIT" - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", - "dev": true, - "license": "ISC" - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", - "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=8.40" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lucide-react": { - "version": "0.560.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.560.0.tgz", - "integrity": "sha512-NwKoUA/aBShsdL8WE5lukV2F/tjHzQRlonQs7fkNGI1sCT0Ay4a9Ap3ST2clUUkcY+9eQ0pBe2hybTQd2fmyDA==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", - "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" - }, - "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-refresh": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", - "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz", - "integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==", - "license": "MIT", - "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-router-dom": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.10.1.tgz", - "integrity": "sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==", - "license": "MIT", - "dependencies": { - "react-router": "7.10.1" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sucrase": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tailwindcss": { - "version": "3.4.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", - "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.6", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", - "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/dashboard/package.json b/dashboard/package.json deleted file mode 100644 index 61714dc..0000000 --- a/dashboard/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "dashboard", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" - }, - "dependencies": { - "appwrite": "^21.5.0", - "lucide-react": "^0.560.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^7.10.1" - }, - "devDependencies": { - "@types/react": "^18.2.55", - "@types/react-dom": "^18.2.19", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", - "@vitejs/plugin-react": "^5.1.2", - "autoprefixer": "^10.4.22", - "eslint": "^8.56.0", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.5", - "postcss": "^8.5.6", - "tailwindcss": "^3.4.17", - "typescript": "^5.2.2", - "vite": "^7.3.1" - } -} diff --git a/dashboard/postcss.config.js b/dashboard/postcss.config.js deleted file mode 100644 index 2e7af2b..0000000 --- a/dashboard/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/dashboard/public/vite.svg b/dashboard/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/dashboard/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/dashboard/server.js b/dashboard/server.js deleted file mode 100644 index 9afbca7..0000000 --- a/dashboard/server.js +++ /dev/null @@ -1,47 +0,0 @@ -const http = require('http'); -const fs = require('fs'); -const path = require('path'); - -const PORT = process.env.PORT || 3000; -const PUBLIC_DIR = path.join(__dirname, 'dist'); - -const getContentType = (filePath) => { - const ext = path.extname(filePath).toLowerCase(); - const types = { - '.html': 'text/html', - '.js': 'text/javascript', - '.css': 'text/css', - '.json': 'application/json', - '.png': 'image/png', - '.jpg': 'image/jpg', - '.gif': 'image/gif', - '.svg': 'image/svg+xml', - '.ico': 'image/x-icon', - }; - return types[ext] || 'application/octet-stream'; -}; - -const server = http.createServer((req, res) => { - let filePath = path.join(PUBLIC_DIR, req.url === '/' ? 'index.html' : req.url); - - fs.stat(filePath, (err, stats) => { - if (err || !stats.isFile()) { - // SPA Fallback: serve index.html for unknown paths - filePath = path.join(PUBLIC_DIR, 'index.html'); - } - - fs.readFile(filePath, (err, content) => { - if (err) { - res.writeHead(500); - res.end('Server Error'); - } else { - res.writeHead(200, { 'Content-Type': getContentType(filePath) }); - res.end(content, 'utf-8'); - } - }); - }); -}); - -server.listen(PORT, () => { - console.log(`Server running on port ${PORT}`); -}); diff --git a/dashboard/src/App.css b/dashboard/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/dashboard/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx deleted file mode 100644 index 798472d..0000000 --- a/dashboard/src/App.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom' -import DashboardLayout from './layouts/DashboardLayout' -import { PrivateRoute } from './contexts/Auth' -import AccountsAdmin from './pages/AccountsAdmin' -import Cloudflare from './pages/Cloudflare' -import ERPFinance from './pages/ERPFinance' -import Github from './pages/Github' -import Hello from './pages/Hello' -import Home from './pages/Home' -import Kanban from './pages/Kanban' -import Login from './pages/Login' -import Profile from './pages/Profile' -import Projects from './pages/Projects' -import Settings from './pages/Settings' - -function App() { - return ( - - - } /> - - }> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - } /> - - - ) -} - -export default App diff --git a/dashboard/src/assets/react.svg b/dashboard/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/dashboard/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/dashboard/src/components/TerminalLogs.tsx b/dashboard/src/components/TerminalLogs.tsx deleted file mode 100644 index 32687d1..0000000 --- a/dashboard/src/components/TerminalLogs.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useEffect, useMemo, useState } from 'react' -import { client, appwriteCollectionAuditLogsId, appwriteDatabaseId } from '../lib/appwrite' - -type TerminalLog = { - id: string - message: string - timestamp: string - userId?: string -} - -const formatTime = (timestamp: string) => { - const date = new Date(timestamp) - if (Number.isNaN(date.getTime())) return timestamp - return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }) -} - -export function TerminalLogs() { - const [open, setOpen] = useState(true) - const [logs, setLogs] = useState([]) - - const logTitle = useMemo(() => (open ? 'Ocultar Terminal' : 'Mostrar Terminal'), [open]) - - useEffect(() => { - if (!appwriteDatabaseId || !appwriteCollectionAuditLogsId || !client) return undefined - - const channel = `databases.${appwriteDatabaseId}.collections.${appwriteCollectionAuditLogsId}.documents` - const unsubscribe = client.subscribe(channel, (response) => { - const payload = (response as { payload?: Record }).payload || {} - const action = (payload.event as string) || (payload.action as string) || 'evento' - const timestamp = (payload.timestamp as string) || new Date().toISOString() - const userId = (payload.user_id as string) || (payload.userId as string) || undefined - - setLogs((prev) => [ - { - id: crypto.randomUUID(), - message: action, - timestamp, - userId, - }, - ...prev, - ].slice(0, 120)) - }) - - return () => { - unsubscribe() - } - }, []) - - return ( -
- - - {open && ( -
- {logs.length === 0 ? ( -

Aguardando logs em tempo real...

- ) : ( -
    - {logs.map((log) => ( -
  • - [{formatTime(log.timestamp)}]{' '} - {log.message} - {log.userId ? • {log.userId} : null} -
  • - ))} -
- )} -
- )} -
- ) -} diff --git a/dashboard/src/components/UserDropdown.tsx b/dashboard/src/components/UserDropdown.tsx deleted file mode 100644 index af32d31..0000000 --- a/dashboard/src/components/UserDropdown.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { ChevronDown, LogOut, Settings, User } from 'lucide-react' -import { useState, useRef, useEffect } from 'react' -import { useNavigate } from 'react-router-dom' -import { useAuth } from '../contexts/Auth' - -export default function UserDropdown() { - const [isOpen, setIsOpen] = useState(false) - const dropdownRef = useRef(null) - const { user, logout } = useAuth() - const navigate = useNavigate() - - // Fecha dropdown ao clicar fora - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { - setIsOpen(false) - } - } - - if (isOpen) { - document.addEventListener('mousedown', handleClickOutside) - } - - return () => { - document.removeEventListener('mousedown', handleClickOutside) - } - }, [isOpen]) - - const handleLogout = async () => { - await logout() - navigate('/login') - } - - const getInitials = (identifier?: string) => { - if (identifier) { - return identifier.substring(0, 2).toUpperCase() - } - return 'U' - } - - const initials = getInitials(user?.identifier) - const displayName = user?.identifier?.split('@')[0] || 'Usuário' - const displayEmail = user?.identifier || '' - - return ( -
- {/* Trigger Button */} - - - {/* Dropdown Menu */} - {isOpen && ( -
- {/* User Info Header */} -
-
-
- {initials} -
-
-

{displayName}

-

{displayEmail}

-
-
-
- - {/* Menu Items */} -
- - - -
- - {/* Logout */} -
- -
-
- )} -
- ) -} diff --git a/dashboard/src/contexts/Auth.tsx b/dashboard/src/contexts/Auth.tsx deleted file mode 100644 index 9bc5e44..0000000 --- a/dashboard/src/contexts/Auth.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react' -import { Navigate, Outlet, useLocation } from 'react-router-dom' -import { getCurrentUser, loginUser, logoutUser, User } from '../lib/auth' - -type AuthContextValue = { - user: User | null - loading: boolean - login: (email: string, password: string) => Promise - logout: () => Promise - refresh: () => Promise -} - -const AuthContext = createContext(undefined) - -export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [user, setUser] = useState(null) - const [loading, setLoading] = useState(true) - - const fetchUser = useCallback(async () => { - try { - setLoading(true) - const current = await getCurrentUser() - setUser(current) - } catch (error) { - console.error('Erro ao buscar usuário atual', error) - setUser(null) - } finally { - setLoading(false) - } - }, []) - - useEffect(() => { - fetchUser() - }, [fetchUser]) - - const login = useCallback( - async (email: string, password: string) => { - await loginUser(email, password) - await fetchUser() - }, - [fetchUser], - ) - - const logout = useCallback(async () => { - await logoutUser() - setUser(null) - }, []) - - const value = useMemo( - () => ({ - user, - loading, - login, - logout, - refresh: fetchUser, - }), - [fetchUser, loading, login, logout, user], - ) - - return {children} -} - -// eslint-disable-next-line react-refresh/only-export-components -export const useAuth = () => { - const context = useContext(AuthContext) - if (!context) throw new Error('useAuth deve ser usado dentro de AuthProvider') - return context -} - -export const PrivateRoute: React.FC = () => { - const { user, loading } = useAuth() - const location = useLocation() - - if (loading) { - return ( -
-

Validando sessão...

-
- ) - } - - if (!user) { - return - } - - return -} diff --git a/dashboard/src/index.css b/dashboard/src/index.css deleted file mode 100644 index 3463665..0000000 --- a/dashboard/src/index.css +++ /dev/null @@ -1,25 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { - font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - background-color: #0b1224; - color: #e2e8f0; -} - -body { - margin: 0; - min-height: 100vh; - background: radial-gradient(circle at top left, rgba(56, 189, 248, 0.08), transparent 35%), - radial-gradient(circle at 20% 20%, rgba(59, 130, 246, 0.08), transparent 40%), - #0b1224; -} - -a { - color: inherit; -} - -#root { - min-height: 100vh; -} diff --git a/dashboard/src/layouts/DashboardLayout.tsx b/dashboard/src/layouts/DashboardLayout.tsx deleted file mode 100644 index e5f5321..0000000 --- a/dashboard/src/layouts/DashboardLayout.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Cloud, Github, Home, Settings, Sparkles, Terminal, FolderGit2, KanbanSquare, KeyRound, DollarSign } from 'lucide-react' -import { NavLink, Outlet } from 'react-router-dom' -import { TerminalLogs } from '../components/TerminalLogs' -import UserDropdown from '../components/UserDropdown' -import { useAuth } from '../contexts/Auth' - -const navItems = [ - { label: 'Overview', to: '/', icon: Home }, - { label: 'Projetos', to: '/projects', icon: FolderGit2 }, - { label: 'Kanban', to: '/kanban', icon: KanbanSquare }, - { label: 'Contas', to: '/accounts', icon: KeyRound }, - { label: 'Financeiro', to: '/finance', icon: DollarSign }, - { label: 'Hello World', to: '/hello', icon: Sparkles }, - { label: 'GitHub Repos', to: '/github', icon: Github }, - { label: 'Cloudflare Zones', to: '/cloudflare', icon: Cloud }, - { label: 'Settings', to: '/settings', icon: Settings }, -] - -const activeClass = - 'flex items-center gap-2 rounded-md bg-slate-800/80 px-3 py-2 text-slate-50 shadow-inner shadow-slate-950 border border-slate-700' -const baseClass = - 'flex items-center gap-2 rounded-md px-3 py-2 text-slate-300 hover:bg-slate-800/50 transition-colors duration-150 border border-transparent' - -export default function DashboardLayout() { - const { user } = useAuth() - - return ( -
- - -
-
-
-

Painel de Controle

-

DevOps Orchestration

-
- -
- -
- -
- - -
-
- ) -} diff --git a/dashboard/src/lib/appwrite.ts b/dashboard/src/lib/appwrite.ts deleted file mode 100644 index 2e429f7..0000000 --- a/dashboard/src/lib/appwrite.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Account, AppwriteException, Client, Databases, Functions, Models } from 'appwrite' - -const appwriteEndpoint = import.meta.env.VITE_APPWRITE_ENDPOINT || '' -const appwriteProjectId = import.meta.env.VITE_APPWRITE_PROJECT_ID || '' -const appwriteDatabaseId = import.meta.env.VITE_APPWRITE_DATABASE_ID -const appwriteCollectionServersId = import.meta.env.VITE_APPWRITE_COLLECTION_SERVERS_ID -const appwriteCollectionGithubReposId = import.meta.env.VITE_APPWRITE_COLLECTION_GITHUB_REPOS_ID -const appwriteCollectionAuditLogsId = import.meta.env.VITE_APPWRITE_COLLECTION_AUDIT_LOGS_ID -const appwriteCollectionCloudflareAccountsId = import.meta.env.VITE_APPWRITE_COLLECTION_CLOUDFLARE_ACCOUNTS_ID - -// Appwrite is now optional - Identity Gateway is the primary auth provider -const client = appwriteEndpoint && appwriteProjectId - ? new Client().setEndpoint(appwriteEndpoint).setProject(appwriteProjectId) - : null - -const account = client ? new Account(client) : null -const databases = client ? new Databases(client) : null -const functions = client ? new Functions(client) : null - -const isSessionExpired = (error: unknown) => { - if (error instanceof AppwriteException) { - if (error.code === 401) return true - const message = error.message?.toLowerCase() ?? '' - return message.includes('session') || error.type?.includes('session') || error.type?.includes('token') - } - return false -} - -export const loginUser = async (email: string, password: string) => { - if (!account) throw new Error('Appwrite not configured') - return account.createEmailPasswordSession(email, password) -} - -export const logoutUser = async () => { - if (!account) return - try { - await account.deleteSession('current') - } catch (error) { - if (!isSessionExpired(error)) { - throw error - } - } -} - -export const getCurrentUser = async (): Promise | null> => { - if (!account) return null - try { - return await account.get() - } catch (error) { - if (isSessionExpired(error)) { - return null - } - - throw error - } -} - -export { - account, - client, - databases, - functions, - appwriteDatabaseId, - appwriteCollectionServersId, - appwriteCollectionGithubReposId, - appwriteCollectionAuditLogsId, - appwriteCollectionCloudflareAccountsId, -} diff --git a/dashboard/src/main.tsx b/dashboard/src/main.tsx deleted file mode 100644 index c3a8552..0000000 --- a/dashboard/src/main.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.tsx' -import { AuthProvider } from './contexts/Auth.tsx' -import './index.css' - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - , -) -// Force rebuild Tue Dec 30 18:43:49 -03 2025 diff --git a/dashboard/src/pages/AccountsAdmin.tsx b/dashboard/src/pages/AccountsAdmin.tsx deleted file mode 100644 index d704046..0000000 --- a/dashboard/src/pages/AccountsAdmin.tsx +++ /dev/null @@ -1,210 +0,0 @@ -import { KeyRound, Plus, Cloud, Github, Server, Zap, Eye, EyeOff, Trash2, TestTube } from 'lucide-react' -import { useEffect, useState } from 'react' -import { Query, type Models } from 'appwrite' -import { databases, appwriteDatabaseId } from '../lib/appwrite' - -type Account = Models.Document & { - name: string - provider: 'cloudflare' | 'github' | 'cpanel' | 'directadmin' | 'appwrite' - apiKey: string - endpoint?: string - active: boolean -} - -const COLLECTION_ID = 'cloud_accounts' - -const providerIcons = { - cloudflare: Cloud, - github: Github, - cpanel: Server, - directadmin: Server, - appwrite: Zap, -} - -const providerColors = { - cloudflare: 'text-orange-400', - github: 'text-slate-100', - cpanel: 'text-blue-400', - directadmin: 'text-purple-400', - appwrite: 'text-pink-400', -} - -export default function AccountsAdmin() { - const [accounts, setAccounts] = useState([]) - const [loading, setLoading] = useState(true) - const [showSecret, setShowSecret] = useState>({}) - const [isCreating, setIsCreating] = useState(false) - - useEffect(() => { - fetchAccounts() - }, []) - - const fetchAccounts = async () => { - try { - if (!appwriteDatabaseId || !databases) { - setLoading(false) - return - } - const response = await databases.listDocuments( - appwriteDatabaseId, - COLLECTION_ID, - [Query.orderDesc('$createdAt'), Query.limit(100)] - ) - setAccounts(response.documents) - } catch (error) { - console.error('Erro ao carregar contas:', error) - } finally { - setLoading(false) - } - } - - const maskApiKey = (key: string) => { - if (key.length <= 8) return '****' - return `${key.substring(0, 4)}${'*'.repeat(20)}${key.substring(key.length - 4)}` - } - - const toggleSecret = (id: string) => { - setShowSecret(prev => ({ ...prev, [id]: !prev[id] })) - } - - const ProviderIcon = ({ provider }: { provider: Account['provider'] }) => { - const Icon = providerIcons[provider] - const colorClass = providerColors[provider] - return - } - - return ( -
- {/* Header */} -
-
-

Admin

-

Gerenciar Contas

-

- Gerencie credenciais de APIs: Cloudflare, GitHub, cPanel, DirectAdmin e Appwrite. -

-
- -
- - {/* Stats */} -
- {['cloudflare', 'github', 'cpanel', 'directadmin', 'appwrite'].map((provider) => { - const count = accounts.filter(a => a.provider === provider).length - const Icon = providerIcons[provider as Account['provider']] - const colorClass = providerColors[provider as Account['provider']] - return ( -
-
- -

{provider}

-
-

{count}

-
- ) - })} -
- - {/* Accounts List */} -
-

Contas Cadastradas

- - {loading ? ( -

Carregando...

- ) : accounts.length === 0 ? ( -
- -

Nenhuma conta cadastrada

-

Clique em "Nova Conta" para adicionar

-
- ) : ( -
- {accounts.map((account) => ( -
- {/* Info */} -
- -
-

{account.name}

-

- {account.provider.toUpperCase()} - {account.endpoint && ` • ${account.endpoint}`} -

-
-
- - {/* Actions */} -
- {/* API Key */} -
- - {showSecret[account.$id] ? account.apiKey : maskApiKey(account.apiKey)} - - -
- - {/* Status */} - - {account.active ? 'Ativo' : 'Inativo'} - - - {/* Test */} - - - {/* Delete */} - -
-
- ))} -
- )} -
- - {/* Create Modal (simplificado - placeholder) */} - {isCreating && ( -
-
-

Nova Conta

-

- Funcionalidade de criação será implementada em breve. -

- -
-
- )} -
- ) -} diff --git a/dashboard/src/pages/Cloudflare.tsx b/dashboard/src/pages/Cloudflare.tsx deleted file mode 100644 index 8ff3dab..0000000 --- a/dashboard/src/pages/Cloudflare.tsx +++ /dev/null @@ -1,338 +0,0 @@ -import { ID, Query, type Models } from 'appwrite' -import { FormEvent, useEffect, useState } from 'react' -import { - appwriteCollectionCloudflareAccountsId, - appwriteDatabaseId, - appwriteCollectionServersId, - databases, - functions, -} from '../lib/appwrite' - -const badgeClass = (status: string) => - status === 'active' ? 'rounded-full bg-emerald-500/20 px-2 py-1 text-xs text-emerald-200' : 'rounded-full bg-amber-500/20 px-2 py-1 text-xs text-amber-200' - -type Zone = { - id: string - name: string - status: string - paused?: boolean -} - -type Worker = { - name: string - modifiedOn?: string - active?: boolean -} - -export default function Cloudflare() { - const [label, setLabel] = useState('') - const [apiKey, setApiKey] = useState('') - const [credentialAccountId, setCredentialAccountId] = useState('') - const [accountId, setAccountId] = useState('') - const [cloudflareAccountId, setCloudflareAccountId] = useState('') - const [credentials, setCredentials] = useState<{ $id: string; label: string; cloudflareAccountId?: string }[]>([]) - const [zones, setZones] = useState([]) - const [workers, setWorkers] = useState([]) - const [execution, setExecution] = useState(null) - const [error, setError] = useState(null) - const [modalOpen, setModalOpen] = useState(false) - const [newZone, setNewZone] = useState('') - const [onlineCount, setOnlineCount] = useState(null) - - useEffect(() => { - const loadCredentials = async () => { - if (!appwriteDatabaseId || !appwriteCollectionCloudflareAccountsId || !databases) return - try { - const response = await databases.listDocuments(appwriteDatabaseId, appwriteCollectionCloudflareAccountsId, [ - Query.equal('provider', 'cloudflare'), - ]) - const docs = response.documents.map((doc) => ({ - $id: doc.$id, - label: (doc as { label?: string }).label || doc.$id, - cloudflareAccountId: (doc as { cloudflareAccountId?: string }).cloudflareAccountId, - })) - setCredentials(docs) - } catch (err) { - console.error(err) - setError('Falha ao carregar credenciais Cloudflare.') - } - } - - const loadWorkers = async () => { - if (!appwriteDatabaseId || !appwriteCollectionServersId || !databases) return - try { - const servers = await databases.listDocuments( - appwriteDatabaseId, - appwriteCollectionServersId, - [ - Query.equal('status', 'online'), - ], - ) - setOnlineCount(servers.total) - } catch (err) { - console.error(err) - } - } - - loadCredentials() - loadWorkers() - }, []) - - const handleSaveKey = async (event: FormEvent) => { - event.preventDefault() - setError(null) - if (!appwriteDatabaseId || !appwriteCollectionCloudflareAccountsId || !databases) { - setError('Configure VITE_APPWRITE_DATABASE_ID e VITE_APPWRITE_COLLECTION_CLOUDFLARE_ACCOUNTS_ID para salvar a chave de API.') - return - } - - try { - const document = await databases.createDocument(appwriteDatabaseId, appwriteCollectionCloudflareAccountsId, ID.unique(), { - provider: 'cloudflare', - apiKey, - label, - cloudflareAccountId: credentialAccountId || undefined, - }) - setCredentials((prev) => [ - ...prev, - { $id: document.$id, label: label || document.$id, cloudflareAccountId: credentialAccountId || undefined }, - ]) - setLabel('') - setApiKey('') - setCredentialAccountId('') - } catch (err) { - console.error(err) - setError('Não foi possível salvar a API Key.') - } - } - - const handleStatus = async () => { - setError(null) - const selectedAccount = accountId || credentials[0]?.$id - if (!selectedAccount) { - setError('Selecione ou cadastre uma credencial Cloudflare antes de consultar o status.') - return - } - if (!functions) { - setError('Appwrite functions not configured.') - return - } - try { - const executionResult = await functions.createExecution( - 'check-cloudflare-status', - JSON.stringify({ accountId: selectedAccount, cloudflareAccountId }), - ) - setExecution(executionResult) - - const payload = executionResult.responseBody ? JSON.parse(executionResult.responseBody) : {} - setZones((payload.zones as Zone[]) || []) - setWorkers((payload.workers as Worker[]) || []) - } catch (err) { - console.error(err) - setError('Não foi possível checar o status do Cloudflare.') - } - } - - const handleAddZone = () => { - if (!newZone.trim()) return - setZones((prev) => [{ id: crypto.randomUUID(), name: newZone, status: 'pending' }, ...prev]) - setNewZone('') - setModalOpen(false) - } - - const online = zones.some((zone) => zone.status === 'active' && !zone.paused) - - return ( -
-
-

Cloudflare

-

Integração e Status

-

- Salve a API Key criptografada no Appwrite e consulte a função check-cloudflare-status. -

-
- -
-
-

Salvar credencial

-

Armazene a chave com criptografia gerenciada pelo Appwrite.

- -
-
- - setLabel(e.target.value)} - className="mt-1 w-full rounded-lg border border-slate-800 bg-slate-950 px-3 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400" - /> -
-
- - setApiKey(e.target.value)} - required - className="mt-1 w-full rounded-lg border border-slate-800 bg-slate-950 px-3 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400" - /> -
-
- - setCredentialAccountId(e.target.value)} - placeholder="Conta para listar Workers" - className="mt-1 w-full rounded-lg border border-slate-800 bg-slate-950 px-3 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400" - /> -
- -
- {error ?

{error}

: null} -
- -
-

Status das zonas

-

Selecione a credencial e consulte o status em tempo real.

- -
-
- - -
-
- - setCloudflareAccountId(e.target.value)} - className="mt-1 w-full rounded-lg border border-slate-800 bg-slate-950 px-3 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400" - /> -
-
- -
- - -
- - {execution ? ( -
- Execução #{execution.$id} • Status {execution.status} -
- ) : null} -
-
- -
-
-
-

Zonas DNS

- - {online ? 'Online' : 'Aguardando verificação'} - -
-
    - {zones.length === 0 ?
  • Nenhuma zona carregada.
  • : null} - {zones.map((zone) => ( -
  • -
    -

    {zone.name}

    -

    ID: {zone.id}

    -
    - {zone.status} -
  • - ))} -
-
- -
-
-

Workers

- Ativos no Appwrite: {onlineCount ?? '---'} -
-
    - {workers.length === 0 ?
  • Nenhum worker listado.
  • : null} - {workers.map((worker) => ( -
  • -
    -

    {worker.name}

    -

    {worker.modifiedOn}

    -
    - - {worker.active ? 'Ativo' : 'Inativo'} - -
  • - ))} -
-
-
- - {modalOpen ? ( -
-
-

Adicionar Nova Zona

-

Inclua uma entrada manual enquanto a função sincroniza zonas.

- setNewZone(e.target.value)} - placeholder="example.com" - className="mt-3 w-full rounded-lg border border-slate-800 bg-slate-900 px-3 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400" - /> -
- - -
-
-
- ) : null} -
- ) -} diff --git a/dashboard/src/pages/ERPFinance.tsx b/dashboard/src/pages/ERPFinance.tsx deleted file mode 100644 index 33fb31f..0000000 --- a/dashboard/src/pages/ERPFinance.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { DollarSign, TrendingUp, TrendingDown, PieChart, Plus } from 'lucide-react' -import { useState } from 'react' - -type Transaction = { - id: string - description: string - amount: number - type: 'income' | 'expense' - category: string - date: string -} - -const mockTransactions: Transaction[] = [ - { id: '1', description: 'Hospedagem Cloudflare', amount: 120.00, type: 'expense', category: 'Infraestrutura', date: '2024-12-10' }, - { id: '2', description: 'Cliente - Projeto Web', amount: 2500.00, type: 'income', category: 'Serviços', date: '2024-12-09' }, - { id: '3', description: 'Licença GitHub Enterprise', amount: 210.00, type: 'expense', category: 'Software', date: '2024-12-08' }, - { id: '4', description: 'Consultoria DevOps', amount: 1800.00, type: 'income', category: 'Consultoria', date: '2024-12-07' }, -] - -export default function ERPFinance() { - const [transactions] = useState(mockTransactions) - - const totalIncome = transactions.filter(t => t.type === 'income').reduce((acc, t) => acc + t.amount, 0) - const totalExpense = transactions.filter(t => t.type === 'expense').reduce((acc, t) => acc + t.amount, 0) - const balance = totalIncome - totalExpense - - const formatCurrency = (value: number) => { - return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value) - } - - return ( -
- {/* Header */} -
-
-

ERP

-

Financeiro

-

- Gerencie receitas, despesas e acompanhe o fluxo de caixa. -

-
- -
- - {/* Summary Cards */} -
- {/* Total Income */} -
-
- -

Receitas

-
-

{formatCurrency(totalIncome)}

-

Total de entradas

-
- - {/* Total Expense */} -
-
- -

Despesas

-
-

{formatCurrency(totalExpense)}

-

Total de saídas

-
- - {/* Balance */} -
-
- -

Saldo

-
-

= 0 ? 'text-cyan-400' : 'text-red-400'}`}> - {formatCurrency(balance)} -

-

Receitas - Despesas

-
-
- - {/* Transactions Table */} -
-
-

Transações Recentes

- -
- -
- {transactions.length === 0 ? ( -

Nenhuma transação registrada

- ) : ( - transactions.map((transaction) => ( -
- {/* Info */} -
-
- {transaction.type === 'income' ? : } -
-
-

{transaction.description}

-

- {transaction.category} • {new Date(transaction.date).toLocaleDateString('pt-BR')} -

-
-
- - {/* Amount */} -

- {transaction.type === 'income' ? '+' : '-'} {formatCurrency(transaction.amount)} -

-
- )) - )} -
-
- - {/* Categories Quick Stats */} -
- {/* Top Categories */} -
-

Categorias

-
- {['Infraestrutura', 'Serviços', 'Software', 'Consultoria'].map((category, idx) => ( -
- {category} - {idx === 0 ? '37%' : idx === 1 ? '28%' : idx === 2 ? '20%' : '15%'} -
- ))} -
-
- - {/* Monthly Trend Placeholder */} -
-

Tendência Mensal

-
- {[60, 80, 75, 90, 85, 100].map((height, idx) => ( -
- ))} -
-

Últimos 6 meses

-
-
-
- ) -} diff --git a/dashboard/src/pages/Github.tsx b/dashboard/src/pages/Github.tsx deleted file mode 100644 index 0b6aedb..0000000 --- a/dashboard/src/pages/Github.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { type Models } from 'appwrite' -import { useState } from 'react' -import { functions } from '../lib/appwrite' - -const columns = ['Nome', 'Linguagem', 'Stars', 'Último Commit'] - -type Repo = { - id: string | number - name: string - language?: string | null - stargazers_count?: number - stars?: number - pushed_at?: string - last_commit?: string -} - -export default function Github() { - const [accountId, setAccountId] = useState('') - const [execution, setExecution] = useState(null) - const [repos, setRepos] = useState([]) - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - - const handleSync = async () => { - setLoading(true) - setError(null) - - if (!functions) { - setError('Appwrite functions not configured.') - setLoading(false) - return - } - - try { - const executionResult = await functions.createExecution( - 'sync-github', - accountId ? JSON.stringify({ accountId }) : undefined, - ) - setExecution(executionResult) - - const payload = executionResult.responseBody ? JSON.parse(executionResult.responseBody) : {} - const normalizedRepos: Repo[] = (payload.repositories as Repo[] | undefined) || [] - setRepos(normalizedRepos) - } catch (err) { - console.error(err) - setError('Não foi possível sincronizar com o GitHub.') - } finally { - setLoading(false) - } - } - - return ( -
-
-

GitHub

-

Sincronizar Repositórios

-

- Execute a função sync-github via Appwrite Functions e visualize o catálogo retornado. -

- -
- setAccountId(e.target.value)} - placeholder="ID do documento em cloud_accounts (opcional)" - className="w-full rounded-lg border border-slate-800 bg-slate-950 px-3 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400" - /> - -
- - {error ?

{error}

: null} - {execution ? ( -

Execução #{execution.$id} • Status: {execution.status}

- ) : null} -
- -
-
- - - - {columns.map((column) => ( - - ))} - - - - {repos.length === 0 ? ( - - - - ) : ( - repos.map((repo) => ( - - - - - - - )) - )} - -
- {column} -
- Nenhum repositório sincronizado ainda. Execute a função acima para preencher a lista. -
{repo.name}{repo.language ?? 'N/A'}{repo.stars ?? repo.stargazers_count ?? 0} - {repo.last_commit || repo.pushed_at - ? new Date(repo.last_commit || (repo.pushed_at as string)).toLocaleString() - : 'Sem dados'} -
-
-
-
- ) -} diff --git a/dashboard/src/pages/Hello.tsx b/dashboard/src/pages/Hello.tsx deleted file mode 100644 index 335f34a..0000000 --- a/dashboard/src/pages/Hello.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { type Models } from 'appwrite' -import { useState } from 'react' -import { functions } from '../lib/appwrite' - -export default function Hello() { - const [name, setName] = useState('') - const [execution, setExecution] = useState(null) - const [message, setMessage] = useState(null) - const [error, setError] = useState(null) - const [loading, setLoading] = useState(false) - - const handleExecute = async () => { - setLoading(true) - setError(null) - setMessage(null) - - if (!functions) { - setError('Appwrite functions not configured.') - setLoading(false) - return - } - - try { - const executionResult = await functions.createExecution( - 'hello-world', - name.trim() ? JSON.stringify({ name }) : undefined, - ) - - setExecution(executionResult) - - const payload = executionResult.responseBody ? JSON.parse(executionResult.responseBody) : {} - setMessage(payload.message || 'Função executada com sucesso.') - } catch (err) { - console.error(err) - setError('Não foi possível executar a função hello-world.') - } finally { - setLoading(false) - } - } - - return ( -
-
-

Funções

-

Hello World

-

- Teste rápido da função hello-world implantada no Appwrite. -

- -
- setName(e.target.value)} - placeholder="Nome opcional para o cumprimento" - className="w-full rounded-lg border border-slate-800 bg-slate-950 px-3 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400" - /> - -
- - {error ?

{error}

: null} - {execution ? ( -

Execução #{execution.$id} • Status: {execution.status}

- ) : null} -
- -
-

Resultado

- {message ? ( -

{message}

- ) : ( -

Aguardando execução.

- )} -
-
- ) -} diff --git a/dashboard/src/pages/Home.tsx b/dashboard/src/pages/Home.tsx deleted file mode 100644 index c61511f..0000000 --- a/dashboard/src/pages/Home.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { Query, type Models } from 'appwrite' -import { useEffect, useState } from 'react' -import { - appwriteCollectionAuditLogsId, - appwriteCollectionGithubReposId, - appwriteCollectionServersId, - appwriteDatabaseId, - databases, -} from '../lib/appwrite' - -const statCard = (label: string, value: string | number, helper?: string) => ( -
-

{label}

-

{value}

- {helper ?

{helper}

: null} -
-) - -type DeploymentLog = Models.Document & { timestamp?: string } - -type Server = Models.Document & { status?: string } - -type Repo = Models.Document & { $id: string } - -export default function Home() { - const [projectsTotal, setProjectsTotal] = useState(null) - const [activeWorkers, setActiveWorkers] = useState(null) - const [lastDeployment, setLastDeployment] = useState('---') - const [error, setError] = useState(null) - - useEffect(() => { - const missing = [appwriteDatabaseId, appwriteCollectionGithubReposId, appwriteCollectionServersId, appwriteCollectionAuditLogsId] - .filter((value) => !value) - if (missing.length > 0) { - setError('Configure VITE_APPWRITE_DATABASE_ID e as collections de repositórios, servidores e audit logs para carregar a visão geral.') - return - } - - const fetchMetrics = async () => { - try { - if (!databases) { - setError('Appwrite not configured. Configure VITE_APPWRITE_* variables to enable metrics.') - return - } - const [repos, servers, deployments] = await Promise.all([ - databases.listDocuments(appwriteDatabaseId!, appwriteCollectionGithubReposId!, [Query.limit(1)]), - databases.listDocuments(appwriteDatabaseId!, appwriteCollectionServersId!, [Query.limit(200)]), - databases.listDocuments(appwriteDatabaseId!, appwriteCollectionAuditLogsId!, [ - Query.orderDesc('timestamp'), - Query.limit(1), - ]), - ]) - - setProjectsTotal(repos.total) - const onlineWorkers = servers.documents.filter((server) => server.status === 'online').length - setActiveWorkers(onlineWorkers) - - const lastTimestamp = deployments.documents[0]?.timestamp - setLastDeployment(lastTimestamp ? new Date(lastTimestamp).toLocaleString() : 'Sem deploys registrados') - } catch (err) { - console.error(err) - setError('Não foi possível carregar as métricas do painel.') - } - } - - fetchMetrics() - }, []) - - return ( -
-
-

Overview

-

Saúde do Projeto

-

- Consolidação das integrações com GitHub, Cloudflare e Appwrite para acompanhar deploys e automações. -

-
- - {error ?
{error}
: null} - -
- {statCard('Total Repos', projectsTotal ?? '---', 'Quantidade de projetos cadastrados no Appwrite')} - {statCard('Active Workers', activeWorkers ?? '---', 'Workers com status online no banco de servidores')} - {statCard('Last Deployment', lastDeployment)} -
- -
-
-

Deploys recentes

-

Acompanhe as últimas execuções registradas no Appwrite.

-
    -
  • - Build & Deploy - {lastDeployment} -
  • -
  • - Sync GitHub - {projectsTotal ?? '---'} repositórios -
  • -
-
- -
-

Status de integrações

-
-
- Appwrite Database - {appwriteDatabaseId ? 'Configurado' : 'Pendente'} -
-
- GitHub Repos - {projectsTotal ?? '---'} cadastrados -
-
- Realtime Logs - Monitorando audit_logs -
-
-
-
-
- ) -} diff --git a/dashboard/src/pages/Kanban.tsx b/dashboard/src/pages/Kanban.tsx deleted file mode 100644 index 8ff3274..0000000 --- a/dashboard/src/pages/Kanban.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { KanbanSquare, Plus } from 'lucide-react' -import { useState } from 'react' - -type Ticket = { - id: string - title: string - description: string - status: 'backlog' | 'in_progress' | 'done' - priority: 'low' | 'medium' | 'high' - assignee?: string -} - -const mockTickets: Ticket[] = [ - { - id: '1', - title: 'Implementar dropdown de perfil', - description: 'Criar componente UserDropdown com avatar e menu', - status: 'done', - priority: 'high', - assignee: 'Você' - }, - { - id: '2', - title: 'Admin Multi-Plataforma', - description: 'Gerenciar credenciais Cloudflare, GitHub, cPanel', - status: 'in_progress', - priority: 'high' - }, - { - id: '3', - title: 'Página de Projetos', - description: 'Grid com filtros e busca', - status: 'in_progress', - priority: 'medium' - }, - { - id: '4', - title: 'ERP Financeiro', - description: 'Módulo de receitas e despesas', - status: 'backlog', - priority: 'medium' - }, -] - -const columns = [ - { id: 'backlog', title: 'Backlog', icon: '📋' }, - { id: 'in_progress', title: 'Em Progresso', icon: '🏃' }, - { id: 'done', title: 'Concluído', icon: '✅' }, -] as const - -export default function Kanban() { - const [tickets] = useState(mockTickets) - - const getPriorityColor = (priority: Ticket['priority']) => { - switch (priority) { - case 'high': return 'bg-red-500/20 text-red-300 border-red-500/30' - case 'medium': return 'bg-yellow-500/20 text-yellow-300 border-yellow-500/30' - case 'low': return 'bg-blue-500/20 text-blue-300 border-blue-500/30' - } - } - - return ( -
- {/* Header */} -
-
-

Quadro

-

Kanban

-

- Gerencie tasks e acompanhe o progresso do trabalho. -

-
- -
- - {/* Kanban Board */} -
- {columns.map((column) => { - const columnTickets = tickets.filter(t => t.status === column.id) - - return ( -
- {/* Column Header */} -
-
- {column.icon} -

{column.title}

-
- - {columnTickets.length} - -
- - {/* Tickets */} -
- {columnTickets.length === 0 ? ( -
- -

Nenhum ticket

-
- ) : ( - columnTickets.map((ticket) => ( -
- {/* Title */} -

{ticket.title}

- - {/* Description */} -

- {ticket.description} -

- - {/* Footer */} -
- - {ticket.priority} - - {ticket.assignee && ( - {ticket.assignee} - )} -
-
- )) - )} -
-
- ) - })} -
-
- ) -} diff --git a/dashboard/src/pages/Login.tsx b/dashboard/src/pages/Login.tsx deleted file mode 100644 index 3d0a2fa..0000000 --- a/dashboard/src/pages/Login.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { AppwriteException } from 'appwrite' -import { FormEvent, useState } from 'react' -import { useLocation, useNavigate } from 'react-router-dom' -import { useAuth } from '../contexts/Auth' - -export default function Login() { - const { login } = useAuth() - const navigate = useNavigate() - const location = useLocation() - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') - const [error, setError] = useState(null) - const [loading, setLoading] = useState(false) - - const handleSubmit = async (event: FormEvent) => { - event.preventDefault() - setError(null) - setLoading(true) - - try { - await login(email, password) - const redirectTo = (location.state as { from?: { pathname?: string } })?.from?.pathname || '/' - navigate(redirectTo, { replace: true }) - } catch (err) { - const appwriteError = err as AppwriteException - setError(appwriteError.message || 'Falha ao autenticar. Confira as credenciais e tente novamente.') - } finally { - setLoading(false) - } - } - - return ( -
-
-
- -
-
-

Appwrite

-

Acesse o Dashboard

-

- Caixa flutuante estilo blueprint. Autentique-se para visualizar repositórios, zonas Cloudflare e logs. -

- -
-
- - setEmail(e.target.value)} - required - className="w-full rounded-lg border border-cyan-500/30 bg-slate-950/60 px-3 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400 focus:ring-1 focus:ring-cyan-400" - /> -
- -
- - setPassword(e.target.value)} - required - className="w-full rounded-lg border border-cyan-500/30 bg-slate-950/60 px-3 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400 focus:ring-1 focus:ring-cyan-400" - /> -
- - {error ?

{error}

: null} - - -
-
-
-
- ) -} diff --git a/dashboard/src/pages/Profile.tsx b/dashboard/src/pages/Profile.tsx deleted file mode 100644 index 455bcd7..0000000 --- a/dashboard/src/pages/Profile.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { User, Mail, Calendar, Shield } from 'lucide-react' -import { useAuth } from '../contexts/Auth' - -export default function Profile() { - const { user } = useAuth() - - const getInitials = (identifier?: string) => { - if (identifier) { - // If it's an email, use first 2 chars before @ - const atIndex = identifier.indexOf('@') - if (atIndex > 0) { - return identifier.substring(0, 2).toUpperCase() - } - return identifier.substring(0, 2).toUpperCase() - } - return 'U' - } - - const initials = getInitials(user?.identifier) - const displayName = user?.identifier?.split('@')[0] || 'Usuário' - const displayEmail = user?.identifier || 'email@exemplo.com' - const createdAt = 'N/A' // Identity Gateway doesn't return createdAt in /users/me - - return ( -
- {/* Header */} -
-

Perfil

-

Minha Conta

-

- Gerencie suas informações pessoais e preferências da conta. -

-
- - {/* Profile Card */} -
-
- {/* Avatar */} -
- {initials} -
- - {/* Info */} -
-
-

{displayName}

-

{displayEmail}

-
- -
- {/* Email */} -
- -
-

Email

-

{displayEmail}

-
-
- - {/* Created At */} -
- -
-

Membro desde

-

{createdAt}

-
-
- - {/* User ID */} -
- -
-

User ID

-

{user?.id || 'N/A'}

-
-
- - {/* Status */} -
- -
-

Status

-

Ativo

-
-
-
-
-
-
- - {/* Actions */} -
- {/* Edit Profile */} -
-

Editar Perfil

-

- Atualize suas informações pessoais e preferências. -

- -
- - {/* Security */} -
-

Segurança

-

- Gerenciar senha e autenticação de dois fatores. -

- -
-
- - {/* Stats Preview */} -
-

Estatísticas

-
-
-

0

-

Projetos

-
-
-

0

-

Tickets

-
-
-

100%

-

Uptime

-
-
-
-
- ) -} diff --git a/dashboard/src/pages/Projects.tsx b/dashboard/src/pages/Projects.tsx deleted file mode 100644 index 6c44619..0000000 --- a/dashboard/src/pages/Projects.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { FolderGit2, Plus, Search, Archive, Play, Pause } from 'lucide-react' -import { useState } from 'react' - -type Project = { - id: string - name: string - description: string - status: 'active' | 'paused' | 'archived' - repository_url?: string - created_at: string -} - -const mockProjects: Project[] = [ - { - id: '1', - name: 'Core Platform', - description: 'Plataforma DevOps principal com dashboard e integraçõesFoi implementadoo esquema completo para gerenciar múltiplas plataformas.', - status: 'active', - repository_url: 'https://github.com/rede5/core', - created_at: '2024-12-11' - }, - { - id: '2', - name: 'Landing Page', - description: 'Landing page com Fresh e Deno', - status: 'active', - created_at: '2024-12-10' - }, -] - -export default function Projects() { - const [projects] = useState(mockProjects) - const [filter, setFilter] = useState<'all' | Project['status']>('all') - - const filteredProjects = filter === 'all' - ? projects - : projects.filter(p => p.status === filter) - - const getStatusColor = (status: Project['status']) => { - switch (status) { - case 'active': return 'bg-emerald-500/20 text-emerald-300' - case 'paused': return 'bg-yellow-500/20 text-yellow-300' - case 'archived': return 'bg-slate-700/50 text-slate-400' - } - } - - const getStatusIcon = (status: Project['status']) => { - switch (status) { - case 'active': return - case 'paused': return - case 'archived': return - } - } - - return ( -
- {/* Header */} -
-
-

Gestão

-

Projetos

-

- Gerencie seus projetos e repositorios. -

-
- -
- - {/* Filters & Search */} -
-
-
- - -
-
-
- {(['all', 'active', 'paused', 'archived'] as const).map((status) => ( - - ))} -
-
- - {/* Projects Grid */} -
- {filteredProjects.map((project) => ( -
- {/* Icon & Status */} -
-
- -
- - {getStatusIcon(project.status)} - {project.status} - -
- - {/* Info */} -

{project.name}

-

{project.description}

- - {/* Meta */} -
- {new Date(project.created_at).toLocaleDateString('pt-BR')} - {project.repository_url && ( - - GitHub - - )} -
-
- ))} -
- - {filteredProjects.length === 0 && ( -
- -

Nenhum projeto encontrado

-
- )} -
- ) -} diff --git a/dashboard/src/pages/Settings.tsx b/dashboard/src/pages/Settings.tsx deleted file mode 100644 index e8a7917..0000000 --- a/dashboard/src/pages/Settings.tsx +++ /dev/null @@ -1,16 +0,0 @@ -export default function Settings() { - return ( -
-

Settings

-

Preferências e Acesso

-

- Ajustes finos para notificações, tokens e comportamento das integrações. Expanda conforme novas features forem criadas. -

-
    -
  • Dark theme ativo
  • -
  • Logs em tempo real habilitados
  • -
  • Sessão Appwrite protegida
  • -
-
- ) -} diff --git a/dashboard/src/vite-env.d.ts b/dashboard/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/dashboard/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/dashboard/tailwind.config.js b/dashboard/tailwind.config.js deleted file mode 100644 index 7141e45..0000000 --- a/dashboard/tailwind.config.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -export default { - content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], - theme: { - extend: {}, - }, - plugins: [], -} diff --git a/dashboard/tsconfig.json b/dashboard/tsconfig.json deleted file mode 100644 index a7fc6fb..0000000 --- a/dashboard/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/dashboard/tsconfig.node.json b/dashboard/tsconfig.node.json deleted file mode 100644 index 97ede7e..0000000 --- a/dashboard/tsconfig.node.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "strict": true - }, - "include": ["vite.config.ts"] -} diff --git a/dashboard/vite.config.ts b/dashboard/vite.config.ts deleted file mode 100644 index 5a33944..0000000 --- a/dashboard/vite.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], -}) diff --git a/identity-gateway/.dockerignore b/identity-gateway/.dockerignore deleted file mode 100644 index 908313b..0000000 --- a/identity-gateway/.dockerignore +++ /dev/null @@ -1,11 +0,0 @@ -node_modules -dist -.git -.env -.gitignore -Dockerfile -README.md -IDENTITY-GATEWAY.md -docs -migrations -*.log diff --git a/identity-gateway/.env.example b/identity-gateway/.env.example deleted file mode 100644 index 9a41552..0000000 --- a/identity-gateway/.env.example +++ /dev/null @@ -1,10 +0,0 @@ -NODE_ENV=development -PORT=4000 -LOG_LEVEL=info -DATABASE_URL=postgres://identity:identity@localhost:5432/identity_gateway -JWT_ACCESS_SECRET=replace_me_access -JWT_REFRESH_SECRET=replace_me_refresh -JWT_ACCESS_TTL=15m -JWT_REFRESH_TTL=30d -COOKIE_DOMAIN=localhost -COOKIE_SECURE=false diff --git a/identity-gateway/.gitignore b/identity-gateway/.gitignore deleted file mode 100644 index 2033061..0000000 --- a/identity-gateway/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules -dist -.env -*.log -.DS_Store -coverage diff --git a/identity-gateway/Dockerfile b/identity-gateway/Dockerfile deleted file mode 100644 index 6b3fa9d..0000000 --- a/identity-gateway/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -# Dockerfile -# Stage 1: Build the application -FROM docker.io/library/node:20-alpine AS builder - -WORKDIR /app - -COPY package.json tsconfig.json ./ -RUN npm install - -COPY src ./src - -RUN npm run build - -# Stage 2: Install production dependencies -FROM docker.io/library/node:20-alpine AS prod-deps - -WORKDIR /app - -COPY package.json ./ -RUN npm install --omit=dev - -# Stage 3: Run the application -FROM gcr.io/distroless/nodejs20-debian12 - -WORKDIR /app - -ENV NODE_ENV=production - -COPY --from=prod-deps /app/node_modules ./node_modules -COPY --from=builder /app/dist ./dist - -EXPOSE 4000 -CMD ["dist/main.js"] diff --git a/identity-gateway/IDENTITY-GATEWAY.md b/identity-gateway/IDENTITY-GATEWAY.md deleted file mode 100644 index 44262a4..0000000 --- a/identity-gateway/IDENTITY-GATEWAY.md +++ /dev/null @@ -1,89 +0,0 @@ -# IDENTITY-GATEWAY - -O `identity-gateway` é a autoridade central de autenticação e autorização **interna** da plataforma. Ele emite tokens confiáveis para comunicação entre microserviços e gerencia o acesso baseados em roles (RBAC). - -## 📋 Visão Geral - -Este serviço não compete com provedores de identidade públicos (como Auth0 ou Clerk). Ele serve como um "porteiro" para o cluster de serviços internos, garantindo que todas as requisições entre serviços ou vindas de frontends autorizados carreguem um contexto de segurança válido. - -### Arquitetura de Confiança - -```mermaid -graph TD - User[Usuário/Client] -->|Login| Gateway[Identity Gateway] - Gateway -->|Valida Credenciais| DB[(Users DB)] - Gateway -->|Emite JWT| Token[Internal JWT] - - Token -->|Bearer Auth| BaaS[BaaS Control Plane] - Token -->|Bearer Auth| Billing[Billing Core] - Token -->|Bearer Auth| CRM[CRM Core] - - subgraph Trust Boundary - BaaS - Billing - CRM - end -``` - -## 🚀 Estrutura do Projeto - -O projeto é construído com **Fastify** e segue uma arquitetura modular: - -| Diretório | Responsabilidade | -| :--- | :--- | -| `src/core` | Guards, plugins globais e interceptors. | -| `src/modules/auth` | Login, refresh token e recuperação de senha. | -| `src/modules/users` | Gestão de usuários e perfis. | -| `src/modules/rbac` | Roles, Permissions e Policies. | - -## 🛠️ Tecnologias e Otimizações - -- **Backend**: Node.js 20 + Fastify. -- **Database**: PostgreSQL (via `pg` driver ou TypeORM/Prisma). -- **Auth**: JWT (Json Web Tokens) assinados com chaves assimétricas ou segredos fortes. -- **Containerização**: - - Base `gcr.io/distroless/nodejs20-debian12`. - - Execução segura sem acesso a shell. - - Usuário não-privilegiado. - -## 💻 Como Executar - -### Docker (Recomendado) - -```bash -# Build -docker build -t identity-gateway . - -# Run -docker run -p 4000:4000 --env-file .env identity-gateway -``` -Serviço rodando na porta `4000`. - -### Desenvolvimento Local - -1. **Instalar dependências**: - ```bash - npm install - ``` -2. **Configurar ambiente**: - ```bash - cp .env.example .env - ``` -3. **Iniciar**: - ```bash - npm run dev - ``` - -## 🔐 Modelo de Segurança - -1. **JWTs Internos**: Tokens curtos (ex: 15 min) contendo `sub`, `tenantId` e `roles`. -2. **Refresh Tokens**: Tokens longos (ex: 7 dias) opacos e rotativos, armazenados no banco. -3. **Service-to-Service**: Serviços podem usar *Client Credentials* para obter tokens de máquina. - -## 🔧 Detalhes do Dockerfile - -O `Dockerfile` utiliza 3 estágios para garantir o menor tamanho possível: - -1. **Builder**: Compila TypeScript. -2. **Prod Deps**: Instala apenas `dependencies` do package.json. -3. **Runtime**: Copia o build e as dependências para uma imagem Distroless minimalista. diff --git a/identity-gateway/docs/architecture.md b/identity-gateway/docs/architecture.md deleted file mode 100644 index 4fc4cb5..0000000 --- a/identity-gateway/docs/architecture.md +++ /dev/null @@ -1,31 +0,0 @@ -# Architecture - -`identity-gateway` is an internal authority for identity across the SaaS platform. It sits between -human users and internal services, issuing trusted JWTs for service-to-service access. - -## Core responsibilities - -- Central authentication and authorization. -- RBAC and permission enforcement per tenant. -- Token issuance for trusted backend services. -- Provider-agnostic identity validation (local/external). - -## Components - -- **Auth module**: Handles login, refresh, and logout flows. -- **Users module**: Maintains internal user identities and tenant membership. -- **Roles & Permissions**: Defines RBAC primitives and tenant-specific grants. -- **Sessions**: Stores refresh token sessions. -- **Core guards**: Enforces authentication, roles, and permissions. - -## Trust boundaries - -- Only internal services validate JWTs issued by the gateway. -- JWTs are not intended for public client apps without a proxy. - -## Data flow - -1. User authenticates with `identity-gateway`. -2. Gateway validates identity via provider and maps user to tenant. -3. Gateway issues access + refresh tokens. -4. Internal services validate access token claims. diff --git a/identity-gateway/docs/security.md b/identity-gateway/docs/security.md deleted file mode 100644 index 0e7a109..0000000 --- a/identity-gateway/docs/security.md +++ /dev/null @@ -1,23 +0,0 @@ -# Security Model - -## Principles - -- Tokens are internal-only and never exposed to untrusted clients directly. -- Permissions are centrally managed in the gateway. -- Providers only validate identity; they do not set sessions or permissions. - -## JWTs - -- Access tokens are short-lived and contain minimal claims. -- Refresh tokens are stored hashed and revocable. - -## Multi-tenant isolation - -- User membership is scoped by tenant. -- Roles and permissions are evaluated per tenant. - -## Operational safeguards - -- Rotate JWT secrets regularly. -- Use TLS in production. -- Enable HTTP-only cookies for refresh tokens when needed. diff --git a/identity-gateway/docs/token-model.md b/identity-gateway/docs/token-model.md deleted file mode 100644 index 6f5246a..0000000 --- a/identity-gateway/docs/token-model.md +++ /dev/null @@ -1,21 +0,0 @@ -# Token Model - -## Access token claims - -```json -{ - "userId": "uuid", - "tenantId": "uuid", - "roles": ["admin"], - "permissions": ["billing.read", "baas.write"] -} -``` - -- Access tokens are used by internal services only. -- TTL is short (default 15 minutes). - -## Refresh tokens - -- Stored hashed in the database. -- Used to issue new access tokens. -- Revoked on logout or compromise. diff --git a/identity-gateway/migrations/migration.sql b/identity-gateway/migrations/migration.sql deleted file mode 100644 index af57b5f..0000000 --- a/identity-gateway/migrations/migration.sql +++ /dev/null @@ -1,58 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - -CREATE TABLE IF NOT EXISTS tenants ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - name TEXT NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE TABLE IF NOT EXISTS users ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - identifier TEXT NOT NULL UNIQUE, - password_hash TEXT NOT NULL, - status TEXT NOT NULL DEFAULT 'active', - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE TABLE IF NOT EXISTS user_tenants ( - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - PRIMARY KEY (user_id, tenant_id) -); - -CREATE TABLE IF NOT EXISTS roles ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - name TEXT NOT NULL UNIQUE, - description TEXT -); - -CREATE TABLE IF NOT EXISTS permissions ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - name TEXT NOT NULL UNIQUE, - description TEXT -); - -CREATE TABLE IF NOT EXISTS role_permissions ( - role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE, - permission_id UUID NOT NULL REFERENCES permissions(id) ON DELETE CASCADE, - PRIMARY KEY (role_id, permission_id) -); - -CREATE TABLE IF NOT EXISTS user_roles ( - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, - role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - PRIMARY KEY (user_id, tenant_id, role_id) -); - -CREATE TABLE IF NOT EXISTS sessions ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, - refresh_token_hash TEXT NOT NULL, - expires_at TIMESTAMPTZ NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - revoked_at TIMESTAMPTZ -); diff --git a/identity-gateway/package.json b/identity-gateway/package.json deleted file mode 100644 index a053475..0000000 --- a/identity-gateway/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "identity-gateway", - "version": "0.1.0", - "private": true, - "description": "Internal identity gateway for multi-service SaaS platforms", - "type": "commonjs", - "scripts": { - "dev": "ts-node-dev --respawn --transpile-only src/main.ts", - "build": "tsc -p tsconfig.json", - "start": "node dist/main.js", - "lint": "eslint . --ext .ts" - }, - "dependencies": { - "@fastify/cookie": "^11.0.2", - "@fastify/cors": "^11.2.0", - "@fastify/swagger": "^9.6.1", - "@fastify/swagger-ui": "^5.2.3", - "ajv-formats": "^3.0.1", - "bcryptjs": "^2.4.3", - "dotenv": "^16.4.5", - "fastify": "^5.6.2", - "jsonwebtoken": "^9.0.2", - "pg": "^8.12.0", - "pino": "^8.19.0", - "pino-pretty": "^11.2.2" - }, - "devDependencies": { - "@types/bcryptjs": "^2.4.6", - "@types/jsonwebtoken": "^9.0.6", - "@types/pg": "^8.11.6", - "eslint": "^9.6.0", - "ts-node-dev": "^2.0.0", - "typescript": "^5.5.3" - } -} diff --git a/identity-gateway/src/core/auth.guard.ts b/identity-gateway/src/core/auth.guard.ts deleted file mode 100644 index 927a66f..0000000 --- a/identity-gateway/src/core/auth.guard.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { FastifyReply, FastifyRequest } from "fastify"; -import { TokenService } from "./token.service"; - -export const authGuard = (tokenService: TokenService) => { - return async (request: FastifyRequest, reply: FastifyReply) => { - const header = request.headers.authorization; - if (!header?.startsWith("Bearer ")) { - reply.code(401).send({ message: "Missing access token" }); - return; - } - - const token = header.replace("Bearer ", ""); - try { - request.tenantContext = tokenService.verifyAccessToken(token); - } catch (error) { - reply.code(401).send({ message: "Invalid access token" }); - return; - } - }; -}; diff --git a/identity-gateway/src/core/permission.guard.ts b/identity-gateway/src/core/permission.guard.ts deleted file mode 100644 index 96ff957..0000000 --- a/identity-gateway/src/core/permission.guard.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { FastifyReply, FastifyRequest } from "fastify"; -import { getTenantContext } from "./tenant.context"; - -export const permissionGuard = (permissions: string[]) => { - return async (request: FastifyRequest, reply: FastifyReply) => { - const context = getTenantContext(request); - const hasPermission = permissions.some((permission) => - context.permissions.includes(permission) - ); - if (!hasPermission) { - reply.code(403).send({ message: "Missing permission" }); - return; - } - }; -}; diff --git a/identity-gateway/src/core/rbac.guard.ts b/identity-gateway/src/core/rbac.guard.ts deleted file mode 100644 index 14d054d..0000000 --- a/identity-gateway/src/core/rbac.guard.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { FastifyReply, FastifyRequest } from "fastify"; -import { getTenantContext } from "./tenant.context"; - -export const rbacGuard = (roles: string[]) => { - return async (request: FastifyRequest, reply: FastifyReply) => { - const context = getTenantContext(request); - const hasRole = roles.some((role) => context.roles.includes(role)); - if (!hasRole) { - reply.code(403).send({ message: "Insufficient role" }); - return; - } - }; -}; diff --git a/identity-gateway/src/core/tenant.context.ts b/identity-gateway/src/core/tenant.context.ts deleted file mode 100644 index 9f3cb12..0000000 --- a/identity-gateway/src/core/tenant.context.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { FastifyRequest } from "fastify"; - -export interface TenantContext { - userId: string; - tenantId: string; - roles: string[]; - permissions: string[]; -} - -declare module "fastify" { - interface FastifyRequest { - tenantContext?: TenantContext; - } -} - -export const getTenantContext = (request: FastifyRequest) => { - if (!request.tenantContext) { - throw new Error("Tenant context not initialized"); - } - return request.tenantContext; -}; diff --git a/identity-gateway/src/core/token.service.ts b/identity-gateway/src/core/token.service.ts deleted file mode 100644 index ab90544..0000000 --- a/identity-gateway/src/core/token.service.ts +++ /dev/null @@ -1,40 +0,0 @@ -import jwt from "jsonwebtoken"; -import { env } from "../lib/env"; -import { TenantContext } from "./tenant.context"; - -export interface TokenPair { - accessToken: string; - refreshToken: string; -} - -export class TokenService { - signAccessToken(payload: TenantContext) { - return jwt.sign(payload, env.jwtAccessSecret, { - expiresIn: env.jwtAccessTtl as any, - }); - } - - signRefreshToken(payload: TenantContext) { - return jwt.sign( - { - userId: payload.userId, - tenantId: payload.tenantId, - }, - env.jwtRefreshSecret, - { - expiresIn: env.jwtRefreshTtl as any, - } - ); - } - - verifyAccessToken(token: string) { - return jwt.verify(token, env.jwtAccessSecret) as TenantContext; - } - - verifyRefreshToken(token: string) { - return jwt.verify(token, env.jwtRefreshSecret) as { - userId: string; - tenantId: string; - }; - } -} diff --git a/identity-gateway/src/lib/crypto.ts b/identity-gateway/src/lib/crypto.ts deleted file mode 100644 index 596451a..0000000 --- a/identity-gateway/src/lib/crypto.ts +++ /dev/null @@ -1,11 +0,0 @@ -import bcrypt from "bcryptjs"; - -const SALT_ROUNDS = 12; - -export const hashSecret = async (value: string) => { - return bcrypt.hash(value, SALT_ROUNDS); -}; - -export const verifySecret = async (value: string, hash: string) => { - return bcrypt.compare(value, hash); -}; diff --git a/identity-gateway/src/lib/db.ts b/identity-gateway/src/lib/db.ts deleted file mode 100644 index f6499dd..0000000 --- a/identity-gateway/src/lib/db.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Pool } from "pg"; -import { env } from "./env"; - -export const db = new Pool({ - connectionString: env.databaseUrl, -}); - -export const closeDb = async () => { - await db.end(); -}; diff --git a/identity-gateway/src/lib/env.ts b/identity-gateway/src/lib/env.ts deleted file mode 100644 index 3e2f499..0000000 --- a/identity-gateway/src/lib/env.ts +++ /dev/null @@ -1,29 +0,0 @@ -import dotenv from "dotenv"; - -dotenv.config(); - -export const env = { - nodeEnv: process.env.NODE_ENV ?? "development", - port: Number(process.env.PORT ?? 4000), - logLevel: process.env.LOG_LEVEL ?? "info", - databaseUrl: process.env.DATABASE_URL ?? "", - jwtAccessSecret: process.env.JWT_ACCESS_SECRET ?? "", - jwtRefreshSecret: process.env.JWT_REFRESH_SECRET ?? "", - jwtAccessTtl: process.env.JWT_ACCESS_TTL ?? "15m", - jwtRefreshTtl: process.env.JWT_REFRESH_TTL ?? "30d", - cookieDomain: process.env.COOKIE_DOMAIN ?? "", - cookieSecure: process.env.COOKIE_SECURE === "true", -}; - -export const assertEnv = () => { - const required = [ - "DATABASE_URL", - "JWT_ACCESS_SECRET", - "JWT_REFRESH_SECRET", - ]; - - const missing = required.filter((key) => !process.env[key]); - if (missing.length > 0) { - throw new Error(`Missing environment variables: ${missing.join(", ")}`); - } -}; diff --git a/identity-gateway/src/lib/logger.ts b/identity-gateway/src/lib/logger.ts deleted file mode 100644 index 9d9fee2..0000000 --- a/identity-gateway/src/lib/logger.ts +++ /dev/null @@ -1,13 +0,0 @@ -import pino from "pino"; -import { env } from "./env"; - -export const logger = pino({ - level: env.logLevel, - transport: - env.nodeEnv === "production" - ? undefined - : { - target: "pino-pretty", - options: { colorize: true }, - }, -}); diff --git a/identity-gateway/src/main.ts b/identity-gateway/src/main.ts deleted file mode 100644 index f0d87fb..0000000 --- a/identity-gateway/src/main.ts +++ /dev/null @@ -1,136 +0,0 @@ -import Fastify from "fastify"; -import cookie from "@fastify/cookie"; -import cors from "@fastify/cors"; -import swagger from "@fastify/swagger"; -import swaggerUi from "@fastify/swagger-ui"; -import { assertEnv, env } from "./lib/env"; -import { logger } from "./lib/logger"; -import { seed } from "./lib/seed"; -import { TokenService } from "./core/token.service"; -import { UserService } from "./modules/users/user.service"; -import { SessionService } from "./modules/sessions/session.service"; -import { AuthService } from "./modules/auth/auth.service"; -import { LocalProvider } from "./modules/providers/local.provider"; -import { AuthProvider } from "./modules/providers/provider.interface"; -import { registerAuthRoutes } from "./modules/auth/auth.controller"; -import { registerUserRoutes } from "./modules/users/user.controller"; -import { TenantService } from "./modules/tenants/tenant.service"; -import { registerTenantRoutes } from "./modules/tenants/tenant.controller"; -import { RoleService } from "./modules/roles/role.service"; -import { registerRoleRoutes } from "./modules/roles/role.controller"; - -const VERSION = "0.1.0"; - -const bootstrap = async () => { - assertEnv(); - - const app = Fastify({ - logger: { - level: process.env.LOG_LEVEL || "info", - ...(process.env.NODE_ENV !== "production" && { - transport: { - target: "pino-pretty", - options: { colorize: true }, - }, - }), - }, - }); - - // CORS - Allow dashboard to access identity gateway - await app.register(cors, { - origin: [ - "https://dev.rede5.com.br", - "https://dashboard.rede5.com.br", - "http://localhost:5173", - "http://localhost:3000", - ], - credentials: true, - methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], - allowedHeaders: ["Content-Type", "Authorization"], - }); - - // Swagger - await app.register(swagger, { - openapi: { - info: { - title: "Identity Gateway API", - description: "Internal identity gateway for multi-service SaaS platforms", - version: VERSION, - }, - servers: [{ url: `https://ig-dev.rede5.com.br` }], - }, - }); - - await app.register(swaggerUi, { - routePrefix: "/docs", - uiConfig: { - docExpansion: "list", - deepLinking: false, - }, - }); - - app.register(cookie); - - // AJV Formats for UUID validation - const Ajv = require("ajv"); - const ajv = new Ajv({ - removeAdditional: true, - useDefaults: true, - coerceTypes: true, - allErrors: true, - }); - require("ajv-formats")(ajv); - app.setValidatorCompiler(({ schema }) => { - return ajv.compile(schema); - }); - - // Root endpoint - app.get("/", async (request) => { - const ip = (request.headers["x-forwarded-for"] as string)?.split(",")[0] || - request.headers["x-real-ip"] || - request.ip; - return { - message: "🔐 Identity Gateway is running!", - version: VERSION, - docs: "/docs", - health: "/health", - ip, - }; - }); - - // Health endpoint - app.get("/health", async () => { - return { status: "ok", timestamp: new Date().toISOString() }; - }); - - const tokenService = new TokenService(); - const userService = new UserService(); - const sessionService = new SessionService(); - const tenantService = new TenantService(); - const roleService = new RoleService(); - - const providers = new Map(); - providers.set("local", new LocalProvider(userService)); - - const authService = new AuthService( - tokenService, - userService, - sessionService, - providers - ); - - registerAuthRoutes(app as any, authService); - registerUserRoutes(app as any, userService, tokenService); - registerTenantRoutes(app as any, tenantService, tokenService); - registerRoleRoutes(app as any, roleService, tokenService); - - // Run seed on startup - await seed(); - - await app.listen({ port: env.port, host: "0.0.0.0" }); -}; - -bootstrap().catch((error) => { - logger.error(error, "Failed to start identity-gateway"); - process.exit(1); -}); diff --git a/identity-gateway/src/modules/auth/auth.controller.ts b/identity-gateway/src/modules/auth/auth.controller.ts deleted file mode 100644 index 36e8746..0000000 --- a/identity-gateway/src/modules/auth/auth.controller.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { FastifyInstance } from "fastify"; -import { AuthService } from "./auth.service"; -import { env } from "../../lib/env"; - -export const registerAuthRoutes = (app: FastifyInstance, authService: AuthService) => { - app.post("/auth/login", { - schema: { - body: { - type: "object", - required: ["identifier", "secret", "tenantId"], - properties: { - provider: { type: "string" }, - identifier: { type: "string" }, - secret: { type: "string" }, - tenantId: { type: "string", format: "uuid" }, - }, - }, - }, - }, async (request, reply) => { - const body = request.body as { - provider?: string; - identifier: string; - secret: string; - tenantId: string; - }; - - const provider = body.provider ?? "local"; - const { accessToken, refreshToken } = await authService.authenticate( - provider, - body.identifier, - body.secret, - body.tenantId - ); - - if (env.cookieDomain) { - reply.setCookie("refresh_token", refreshToken, { - httpOnly: true, - secure: env.cookieSecure, - sameSite: "strict", - domain: env.cookieDomain, - path: "/", - }); - } - - return { accessToken, refreshToken }; - }); - - app.post("/auth/refresh", async (request, reply) => { - const body = request.body as { refreshToken?: string }; - const refreshToken = body.refreshToken ?? request.cookies.refresh_token; - if (!refreshToken) { - reply.code(400).send({ message: "Missing refresh token" }); - return; - } - - const { accessToken } = await authService.refresh(refreshToken); - return { accessToken }; - }); - - app.post("/auth/logout", async (request, reply) => { - const body = request.body as { refreshToken?: string }; - const refreshToken = body.refreshToken ?? request.cookies.refresh_token; - if (!refreshToken) { - reply.code(400).send({ message: "Missing refresh token" }); - return; - } - - await authService.revoke(refreshToken); - reply.clearCookie("refresh_token", { - domain: env.cookieDomain || undefined, - path: "/", - }); - return { success: true }; - }); -}; diff --git a/identity-gateway/src/modules/auth/auth.service.ts b/identity-gateway/src/modules/auth/auth.service.ts deleted file mode 100644 index 165a515..0000000 --- a/identity-gateway/src/modules/auth/auth.service.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { TokenService } from "../../core/token.service"; -import { AuthProvider } from "../providers/provider.interface"; -import { SessionService } from "../sessions/session.service"; -import { UserService } from "../users/user.service"; - -export class AuthService { - constructor( - private readonly tokenService: TokenService, - private readonly userService: UserService, - private readonly sessionService: SessionService, - private readonly providers: Map - ) {} - - async authenticate(providerName: string, identifier: string, secret: string, tenantId: string) { - const provider = this.providers.get(providerName); - if (!provider) { - throw new Error("Auth provider not supported"); - } - - const identity = await provider.validate({ identifier, secret }, tenantId); - const roles = await this.userService.listRolesForTenant(identity.userId, tenantId); - const permissions = await this.userService.listPermissionsForTenant(identity.userId, tenantId); - - const context = { - userId: identity.userId, - tenantId, - roles, - permissions, - }; - - const accessToken = this.tokenService.signAccessToken(context); - const refreshToken = this.tokenService.signRefreshToken(context); - - const refreshExpiry = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30); - await this.sessionService.createSession(identity.userId, tenantId, refreshToken, refreshExpiry); - - return { accessToken, refreshToken, context }; - } - - async refresh(refreshToken: string) { - const payload = this.tokenService.verifyRefreshToken(refreshToken); - const session = await this.sessionService.findValidSession( - payload.userId, - payload.tenantId, - refreshToken - ); - - if (!session) { - throw new Error("Invalid refresh token"); - } - - const roles = await this.userService.listRolesForTenant(payload.userId, payload.tenantId); - const permissions = await this.userService.listPermissionsForTenant(payload.userId, payload.tenantId); - - const context = { - userId: payload.userId, - tenantId: payload.tenantId, - roles, - permissions, - }; - - const accessToken = this.tokenService.signAccessToken(context); - return { accessToken, context }; - } - - async revoke(refreshToken: string) { - const payload = this.tokenService.verifyRefreshToken(refreshToken); - const session = await this.sessionService.findValidSession( - payload.userId, - payload.tenantId, - refreshToken - ); - - if (session) { - await this.sessionService.revokeSession(session.id); - } - } -} diff --git a/identity-gateway/src/modules/permissions/permission.entity.ts b/identity-gateway/src/modules/permissions/permission.entity.ts deleted file mode 100644 index 2f89056..0000000 --- a/identity-gateway/src/modules/permissions/permission.entity.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface PermissionEntity { - id: string; - name: string; - description?: string; -} diff --git a/identity-gateway/src/modules/permissions/permission.service.ts b/identity-gateway/src/modules/permissions/permission.service.ts deleted file mode 100644 index 5ba5ee2..0000000 --- a/identity-gateway/src/modules/permissions/permission.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { db } from "../../lib/db"; -import { PermissionEntity } from "./permission.entity"; - -export class PermissionService { - async createPermission(name: string, description?: string) { - const result = await db.query( - "INSERT INTO permissions (name, description) VALUES ($1, $2) RETURNING id, name, description", - [name, description ?? null] - ); - return result.rows[0]; - } - - async listPermissions() { - const result = await db.query( - "SELECT id, name, description FROM permissions ORDER BY name" - ); - return result.rows; - } -} diff --git a/identity-gateway/src/modules/providers/external.provider.ts b/identity-gateway/src/modules/providers/external.provider.ts deleted file mode 100644 index 5d97592..0000000 --- a/identity-gateway/src/modules/providers/external.provider.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AuthProvider, ProviderCredentials, ProviderIdentity } from "./provider.interface"; - -export class ExternalProvider implements AuthProvider { - name = "external"; - - async validate( - _credentials: ProviderCredentials, - _tenantId: string - ): Promise { - throw new Error("External provider not configured"); - } -} diff --git a/identity-gateway/src/modules/providers/local.provider.ts b/identity-gateway/src/modules/providers/local.provider.ts deleted file mode 100644 index dcef2bc..0000000 --- a/identity-gateway/src/modules/providers/local.provider.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AuthProvider, ProviderCredentials, ProviderIdentity } from "./provider.interface"; -import { UserService } from "../users/user.service"; - -export class LocalProvider implements AuthProvider { - name = "local"; - - constructor(private readonly userService: UserService) {} - - async validate( - credentials: ProviderCredentials, - tenantId: string - ): Promise { - const user = await this.userService.verifyCredentials( - credentials.identifier, - credentials.secret - ); - - const isMember = await this.userService.isMemberOfTenant(user.id, tenantId); - if (!isMember) { - throw new Error("User is not assigned to tenant"); - } - - return { userId: user.id, tenantId }; - } -} diff --git a/identity-gateway/src/modules/providers/provider.interface.ts b/identity-gateway/src/modules/providers/provider.interface.ts deleted file mode 100644 index 285192f..0000000 --- a/identity-gateway/src/modules/providers/provider.interface.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface ProviderIdentity { - userId: string; - tenantId: string; -} - -export interface ProviderCredentials { - identifier: string; - secret: string; -} - -export interface AuthProvider { - name: string; - validate(credentials: ProviderCredentials, tenantId: string): Promise; -} diff --git a/identity-gateway/src/modules/roles/role.entity.ts b/identity-gateway/src/modules/roles/role.entity.ts deleted file mode 100644 index dba0364..0000000 --- a/identity-gateway/src/modules/roles/role.entity.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface RoleEntity { - id: string; - name: string; - description?: string; -} diff --git a/identity-gateway/src/modules/roles/role.service.ts b/identity-gateway/src/modules/roles/role.service.ts deleted file mode 100644 index ea97aa7..0000000 --- a/identity-gateway/src/modules/roles/role.service.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { db } from "../../lib/db"; - -export interface RoleEntity { - id: string; - name: string; - description: string | null; -} - -export class RoleService { - async createRole(name: string, description?: string): Promise { - const result = await db.query( - "INSERT INTO roles (name, description) VALUES ($1, $2) RETURNING id, name, description", - [name, description || null] - ); - return result.rows[0]; - } - - async findById(id: string): Promise { - const result = await db.query( - "SELECT id, name, description FROM roles WHERE id = $1", - [id] - ); - if (result.rowCount === 0) { - throw new Error("Role not found"); - } - return result.rows[0]; - } - - async findByName(name: string): Promise { - const result = await db.query( - "SELECT id, name, description FROM roles WHERE name = $1", - [name] - ); - return result.rows[0] || null; - } - - async listRoles(): Promise { - const result = await db.query( - "SELECT id, name, description FROM roles ORDER BY name" - ); - return result.rows; - } - - async updateRole(id: string, name: string, description?: string): Promise { - const result = await db.query( - "UPDATE roles SET name = $2, description = $3 WHERE id = $1 RETURNING id, name, description", - [id, name, description || null] - ); - if (result.rowCount === 0) { - throw new Error("Role not found"); - } - return result.rows[0]; - } - - async deleteRole(id: string): Promise { - const result = await db.query("DELETE FROM roles WHERE id = $1", [id]); - if (result.rowCount === 0) { - throw new Error("Role not found"); - } - } - - async assignRoleToUser(userId: string, tenantId: string, roleId: string): Promise { - await db.query( - "INSERT INTO user_roles (user_id, tenant_id, role_id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", - [userId, tenantId, roleId] - ); - } - - async removeRoleFromUser(userId: string, tenantId: string, roleId: string): Promise { - await db.query( - "DELETE FROM user_roles WHERE user_id = $1 AND tenant_id = $2 AND role_id = $3", - [userId, tenantId, roleId] - ); - } -} diff --git a/identity-gateway/src/modules/sessions/session.entity.ts b/identity-gateway/src/modules/sessions/session.entity.ts deleted file mode 100644 index 290a1be..0000000 --- a/identity-gateway/src/modules/sessions/session.entity.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface SessionEntity { - id: string; - userId: string; - tenantId: string; - refreshTokenHash: string; - expiresAt: string; - createdAt: string; - revokedAt?: string | null; -} diff --git a/identity-gateway/src/modules/sessions/session.service.ts b/identity-gateway/src/modules/sessions/session.service.ts deleted file mode 100644 index 4488841..0000000 --- a/identity-gateway/src/modules/sessions/session.service.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { db } from "../../lib/db"; -import { hashSecret, verifySecret } from "../../lib/crypto"; -import { SessionEntity } from "./session.entity"; - -export class SessionService { - async createSession(userId: string, tenantId: string, refreshToken: string, expiresAt: Date) { - const refreshTokenHash = await hashSecret(refreshToken); - const result = await db.query( - `INSERT INTO sessions (user_id, tenant_id, refresh_token_hash, expires_at) - VALUES ($1, $2, $3, $4) - RETURNING id, user_id as "userId", tenant_id as "tenantId", refresh_token_hash as "refreshTokenHash", expires_at as "expiresAt", created_at as "createdAt", revoked_at as "revokedAt"`, - [userId, tenantId, refreshTokenHash, expiresAt] - ); - return result.rows[0]; - } - - async findValidSession(userId: string, tenantId: string, refreshToken: string) { - const result = await db.query( - `SELECT id, user_id as "userId", tenant_id as "tenantId", refresh_token_hash as "refreshTokenHash", expires_at as "expiresAt", created_at as "createdAt", revoked_at as "revokedAt" - FROM sessions - WHERE user_id = $1 AND tenant_id = $2 AND revoked_at IS NULL AND expires_at > NOW() - ORDER BY created_at DESC - LIMIT 5`, - [userId, tenantId] - ); - - for (const session of result.rows) { - const valid = await verifySecret(refreshToken, session.refreshTokenHash); - if (valid) { - return session; - } - } - - return null; - } - - async revokeSession(sessionId: string) { - await db.query("UPDATE sessions SET revoked_at = NOW() WHERE id = $1", [sessionId]); - } -} diff --git a/identity-gateway/src/modules/users/user.controller.ts b/identity-gateway/src/modules/users/user.controller.ts deleted file mode 100644 index 230dc3d..0000000 --- a/identity-gateway/src/modules/users/user.controller.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { FastifyInstance } from "fastify"; -import { UserService } from "./user.service"; -import { authGuard } from "../../core/auth.guard"; -import { TokenService } from "../../core/token.service"; -import { getTenantContext } from "../../core/tenant.context"; - -export const registerUserRoutes = ( - app: FastifyInstance, - userService: UserService, - tokenService: TokenService -) => { - // Get current user - app.get( - "/users/me", - { preHandler: authGuard(tokenService) }, - async (request) => { - const context = getTenantContext(request); - const user = await userService.findById(context.userId); - return { - id: user.id, - identifier: user.identifier, - status: user.status, - }; - } - ); - - // List all users - app.get( - "/users", - { preHandler: authGuard(tokenService) }, - async () => { - return userService.listUsers(); - } - ); - - // Get user by ID - app.get( - "/users/:id", - { preHandler: authGuard(tokenService) }, - async (request) => { - const { id } = request.params as { id: string }; - const user = await userService.findById(id); - return { - id: user.id, - identifier: user.identifier, - status: user.status, - createdAt: user.createdAt, - }; - } - ); - - // Create user - app.post( - "/users", - { preHandler: authGuard(tokenService) }, - async (request, reply) => { - const { identifier, password, tenantId } = request.body as { - identifier: string; - password: string; - tenantId?: string; - }; - if (!identifier || !password) { - reply.code(400).send({ message: "identifier and password are required" }); - return; - } - const user = await userService.createUser(identifier, password); - if (tenantId) { - await userService.addUserToTenant(user.id, tenantId); - } - reply.code(201).send({ - id: user.id, - identifier: user.identifier, - status: user.status, - }); - } - ); - - // Update user - app.put( - "/users/:id", - { preHandler: authGuard(tokenService) }, - async (request) => { - const { id } = request.params as { id: string }; - const { identifier, status, password } = request.body as { - identifier?: string; - status?: string; - password?: string; - }; - return userService.updateUser(id, { identifier, status, password }); - } - ); - - // Delete user - app.delete( - "/users/:id", - { preHandler: authGuard(tokenService) }, - async (request, reply) => { - const { id } = request.params as { id: string }; - await userService.deleteUser(id); - reply.code(204).send(); - } - ); - - // Add user to tenant - app.post( - "/users/:userId/tenants/:tenantId", - { preHandler: authGuard(tokenService) }, - async (request, reply) => { - const { userId, tenantId } = request.params as { userId: string; tenantId: string }; - await userService.addUserToTenant(userId, tenantId); - reply.code(201).send({ success: true }); - } - ); - - // Remove user from tenant - app.delete( - "/users/:userId/tenants/:tenantId", - { preHandler: authGuard(tokenService) }, - async (request, reply) => { - const { userId, tenantId } = request.params as { userId: string; tenantId: string }; - await userService.removeUserFromTenant(userId, tenantId); - reply.code(204).send(); - } - ); -}; diff --git a/identity-gateway/src/modules/users/user.entity.ts b/identity-gateway/src/modules/users/user.entity.ts deleted file mode 100644 index dd1a7a0..0000000 --- a/identity-gateway/src/modules/users/user.entity.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface UserEntity { - id: string; - identifier: string; - passwordHash: string; - status: "active" | "disabled"; - createdAt: string; -} diff --git a/identity-gateway/src/modules/users/user.service.ts b/identity-gateway/src/modules/users/user.service.ts deleted file mode 100644 index 6479f02..0000000 --- a/identity-gateway/src/modules/users/user.service.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { db } from "../../lib/db"; -import { hashSecret, verifySecret } from "../../lib/crypto"; -import { UserEntity } from "./user.entity"; - -export class UserService { - async createUser(identifier: string, password: string) { - const passwordHash = await hashSecret(password); - const result = await db.query( - "INSERT INTO users (identifier, password_hash) VALUES ($1, $2) RETURNING id, identifier, password_hash as \"passwordHash\", status, created_at as \"createdAt\"", - [identifier, passwordHash] - ); - return result.rows[0]; - } - - async findByIdentifier(identifier: string) { - const result = await db.query( - "SELECT id, identifier, password_hash as \"passwordHash\", status, created_at as \"createdAt\" FROM users WHERE identifier = $1", - [identifier] - ); - if (result.rowCount === 0) { - throw new Error("User not found"); - } - return result.rows[0]; - } - - async findById(userId: string) { - const result = await db.query( - "SELECT id, identifier, password_hash as \"passwordHash\", status, created_at as \"createdAt\" FROM users WHERE id = $1", - [userId] - ); - if (result.rowCount === 0) { - throw new Error("User not found"); - } - return result.rows[0]; - } - - async listUsers() { - const result = await db.query( - "SELECT id, identifier, status, created_at as \"createdAt\" FROM users ORDER BY created_at DESC" - ); - return result.rows; - } - - async updateUser(userId: string, updates: { identifier?: string; status?: string; password?: string }) { - const user = await this.findById(userId); - const newIdentifier = updates.identifier ?? user.identifier; - const newStatus = updates.status ?? user.status; - let newPasswordHash = user.passwordHash; - - if (updates.password) { - newPasswordHash = await hashSecret(updates.password); - } - - const result = await db.query( - "UPDATE users SET identifier = $2, status = $3, password_hash = $4 WHERE id = $1 RETURNING id, identifier, status, created_at as \"createdAt\"", - [userId, newIdentifier, newStatus, newPasswordHash] - ); - return result.rows[0]; - } - - async deleteUser(userId: string) { - const result = await db.query("DELETE FROM users WHERE id = $1", [userId]); - if (result.rowCount === 0) { - throw new Error("User not found"); - } - } - - async addUserToTenant(userId: string, tenantId: string) { - await db.query( - "INSERT INTO user_tenants (user_id, tenant_id) VALUES ($1, $2) ON CONFLICT DO NOTHING", - [userId, tenantId] - ); - } - - async removeUserFromTenant(userId: string, tenantId: string) { - await db.query( - "DELETE FROM user_tenants WHERE user_id = $1 AND tenant_id = $2", - [userId, tenantId] - ); - } - - async verifyCredentials(identifier: string, password: string) { - const user = await this.findByIdentifier(identifier); - if (user.status !== "active") { - throw new Error("User disabled"); - } - const valid = await verifySecret(password, user.passwordHash); - if (!valid) { - throw new Error("Invalid credentials"); - } - return user; - } - - async isMemberOfTenant(userId: string, tenantId: string) { - const result = await db.query<{ exists: boolean }>( - "SELECT EXISTS (SELECT 1 FROM user_tenants WHERE user_id = $1 AND tenant_id = $2) as \"exists\"", - [userId, tenantId] - ); - return result.rows[0]?.exists ?? false; - } - - async listRolesForTenant(userId: string, tenantId: string) { - const result = await db.query<{ name: string }>( - `SELECT roles.name - FROM user_roles - JOIN roles ON roles.id = user_roles.role_id - WHERE user_roles.user_id = $1 AND user_roles.tenant_id = $2`, - [userId, tenantId] - ); - return result.rows.map((row) => row.name); - } - - async listPermissionsForTenant(userId: string, tenantId: string) { - const result = await db.query<{ name: string }>( - `SELECT permissions.name - FROM user_roles - JOIN role_permissions ON role_permissions.role_id = user_roles.role_id - JOIN permissions ON permissions.id = role_permissions.permission_id - WHERE user_roles.user_id = $1 AND user_roles.tenant_id = $2`, - [userId, tenantId] - ); - return result.rows.map((row) => row.name); - } -} diff --git a/identity-gateway/tsconfig.json b/identity-gateway/tsconfig.json deleted file mode 100644 index 649771d..0000000 --- a/identity-gateway/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "moduleResolution": "node", - "outDir": "dist", - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - }, - "include": [ - "src/**/*.ts" - ] -} \ No newline at end of file diff --git a/observability-core/.dockerignore b/observability-core/.dockerignore deleted file mode 100644 index 1f094c4..0000000 --- a/observability-core/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -.git -.env -.gitignore -Dockerfile -README.md -OBSERVABILITY-CORE.md -migrations -*.log diff --git a/observability-core/.env.example b/observability-core/.env.example deleted file mode 100644 index 097e96f..0000000 --- a/observability-core/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -DATABASE_URL= -PORT=8080 diff --git a/observability-core/.gitignore b/observability-core/.gitignore deleted file mode 100644 index 5340f42..0000000 --- a/observability-core/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.env -*.log -.DS_Store -observability-core -main -coverage diff --git a/observability-core/Dockerfile b/observability-core/Dockerfile deleted file mode 100644 index 56f5e70..0000000 --- a/observability-core/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -# Dockerfile -FROM docker.io/library/golang:1.23-alpine AS builder - -WORKDIR /app - -COPY go.mod go.sum ./ -RUN go mod download - -COPY . . - -# Build with optimization flags -# -w -s: Strip debug symbols -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /app/observability-core ./cmd/api - -# Use Google Distroless static image for minimal size and security -FROM gcr.io/distroless/static:nonroot - -WORKDIR /app - -COPY --from=builder /app/observability-core . -# Copy configs/migrations if needed at runtime, e.g.: -# COPY --from=builder /app/migrations ./migrations - -USER nonroot:nonroot - -EXPOSE 8080 - -CMD ["./observability-core"] diff --git a/observability-core/OBSERVABILITY-CORE.md b/observability-core/OBSERVABILITY-CORE.md deleted file mode 100644 index f38b186..0000000 --- a/observability-core/OBSERVABILITY-CORE.md +++ /dev/null @@ -1,79 +0,0 @@ -# OBSERVABILITY-CORE - -O `observability-core` é o serviço central de monitoramento da plataforma. Ele coleta health checks, gerencia alertas e integrações com TimeSeries DB (TimescaleDB) para armazenar métricas de longo prazo. - -## 📋 Visão Geral - -Diferente de sistemas de log como o `dashboard` (que consome logs via Appwrite Realtime), este serviço foca em *métricas* e *disponibilidade*. - -### Arquitetura - -```mermaid -graph TD - Agent[Internal Service] -->|Push Metrics| API[Observability API] - API -->|Write| TSDB[(TimescaleDB)] - - API -->|Check| Targets[http://target-service/health] - - subgraph Periodic Jobs - Job[Health Checker] -->|Ping| Targets - Job -->|Alert| PagerDuty[Pager Duty / Slack] - end -``` - -## 🚀 Estrutura do Projeto - -O projeto é escrito em **Go** para alta performance e concorrência: - -| Diretório | Descrição | -| :--- | :--- | -| `cmd/api` | Entrypoint da API. | -| `internal/checks` | Lógica de Health Checks (HTTP/TCP ping). | -| `internal/metrics` | Agregação de dados. | -| `db/migrations` | Migrações SQL (Postgres/Timescale). | - -## 🛠️ Tecnologias e Otimizações - -- **Linguagem**: Go 1.23+ -- **Database**: PostgreSQL + TimescaleDB extension. -- **Libraries**: Chi (Router), Pgx (Driver). -- **Containerização**: - - Baseada em `gcr.io/distroless/static:nonroot`. - - Binário estático (`CGO_ENABLED=0`). - - Imagem final < 25MB. - -## 💻 Como Executar - -### Docker (Recomendado) - -```bash -# Build -docker build -t observability-core . - -# Run -docker run -p 8080:8080 --env-file .env observability-core -``` -A API estará disponível na porta `8080`. - -### Desenvolvimento - -1. **Dependências**: - - Go 1.23+ - - PostgreSQL -2. **Setup**: - ```bash - cp .env.example .env - go mod tidy - ``` -3. **Executar**: - ```bash - go run ./cmd/api - ``` - -## 🔧 Detalhes do Dockerfile - -O `Dockerfile` é otimizado para Go: - -- **Builder**: `golang:1.23-alpine`. -- **Flags**: `-ldflags="-w -s"` para remover símbolos de debug. -- **Runtime**: Distroless Static (sem libc, sem shell), garantindo a menor superfície de ataque possível. diff --git a/observability-core/cmd/api/main.go b/observability-core/cmd/api/main.go deleted file mode 100644 index 7e2db12..0000000 --- a/observability-core/cmd/api/main.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "github.com/jackc/pgx/v5/pgxpool" - "github.com/lab/observability-core/internal/alerter" - "github.com/lab/observability-core/internal/collector" - "github.com/lab/observability-core/internal/config" - "github.com/lab/observability-core/internal/db" -) - -type application struct { - config *config.Config - queries *db.Queries -} - -func main() { - cfg := config.Load() - - pool, err := pgxpool.New(context.Background(), cfg.DatabaseURL) - if err != nil { - log.Fatalf("Unable to connect to database: %v\n", err) - } - defer pool.Close() - - queries := db.New(pool) - - app := &application{ - config: cfg, - queries: queries, - } - - coll := collector.New(queries) - coll.Start() - - alert := alerter.New(queries) - alert.Start() - - r := chi.NewRouter() - r.Use(middleware.Logger) - r.Use(middleware.Recoverer) - - r.Get("/", func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Observability Core")) - }) - - r.Post("/targets", app.createTargetHandler) - r.Post("/checks", app.createCheckHandler) - r.Get("/metrics", app.getMetricsHandler) - r.Get("/incidents", app.getIncidentsHandler) - - log.Printf("Starting server on port %s", cfg.Port) - if err := http.ListenAndServe(fmt.Sprintf(":%s", cfg.Port), r); err != nil { - log.Fatalf("Could not start server: %s\n", err) - } -} - -func (app *application) createTargetHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - -func (app *application) createCheckHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - -func (app *application) getMetricsHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - -func (app *application) getIncidentsHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} \ No newline at end of file diff --git a/observability-core/db/query.sql b/observability-core/db/query.sql deleted file mode 100644 index 2777a52..0000000 --- a/observability-core/db/query.sql +++ /dev/null @@ -1,54 +0,0 @@ --- name: CreateTarget :one -INSERT INTO targets (name, type, config) -VALUES ($1, $2, $3) -RETURNING *; - --- name: GetTarget :one -SELECT * FROM targets -WHERE id = $1; - --- name: CreateCheck :one -INSERT INTO checks (target_id, type, interval_seconds, timeout_seconds) -VALUES ($1, $2, $3, $4) -RETURNING *; - --- name: GetCheck :one -SELECT * FROM checks -WHERE id = $1; - --- name: ListChecks :many -SELECT * FROM checks -WHERE is_active = TRUE; - --- name: CreateMetric :one -INSERT INTO metrics (time, check_id, value, tags) -VALUES ($1, $2, $3, $4) -RETURNING *; - --- name: GetMetricsForCheck :many -SELECT * FROM metrics -WHERE check_id = $1 AND time >= $2 AND time <= $3 -ORDER BY time DESC; - --- name: CreateAlertRule :one -INSERT INTO alert_rules (check_id, name, threshold, operator, for_duration, severity) -VALUES ($1, $2, $3, $4, $5, $6) -RETURNING *; - --- name: ListAlertRules :many -SELECT * FROM alert_rules; - --- name: CreateIncident :one -INSERT INTO incidents (alert_rule_id, status, start_time) -VALUES ($1, $2, $3) -RETURNING *; - --- name: GetIncident :one -SELECT * FROM incidents -WHERE id = $1; - --- name: UpdateIncidentStatus :one -UPDATE incidents -SET status = $2, updated_at = NOW() -WHERE id = $1 -RETURNING *; diff --git a/observability-core/go.mod b/observability-core/go.mod deleted file mode 100644 index ced7226..0000000 --- a/observability-core/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/lab/observability-core - -go 1.23 - -require ( - github.com/go-chi/chi/v5 v5.2.3 - github.com/jackc/pgx/v5 v5.7.1 - github.com/joho/godotenv v1.5.1 -) - -require ( - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/text v0.21.0 // indirect -) diff --git a/observability-core/go.sum b/observability-core/go.sum deleted file mode 100644 index afb5e49..0000000 --- a/observability-core/go.sum +++ /dev/null @@ -1,32 +0,0 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= -github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/observability-core/internal/alerter/alerter.go b/observability-core/internal/alerter/alerter.go deleted file mode 100644 index 4f296d6..0000000 --- a/observability-core/internal/alerter/alerter.go +++ /dev/null @@ -1,85 +0,0 @@ -package alerter - -import ( - "context" - "log" - "time" - - "github.com/lab/observability-core/internal/db" -) - -type Alerter struct { - queries *db.Queries -} - -func New(queries *db.Queries) *Alerter { - return &Alerter{queries: queries} -} - -func (a *Alerter) Start() { - log.Println("Starting alerter") - ticker := time.NewTicker(30 * time.Second) // Run every 30 seconds - - go func() { - for range ticker.C { - a.runAlerts() - } - }() -} - -func (a *Alerter) runAlerts() { - rules, err := a.queries.ListAlertRules(context.Background()) - if err != nil { - log.Printf("Error listing alert rules: %v", err) - return - } - - for _, rule := range rules { - go a.evaluateRule(rule) - } -} - -func (a *Alerter) evaluateRule(rule db.AlertRule) { - // This is a very simplified logic. - // It should query metrics within the `for_duration` and check if they meet the threshold. - // For now, we'll just log a message. - log.Printf("Evaluating alert rule: %s", rule.Name) - - // A real implementation would look something like this: - /* - params := db.GetMetricsForCheckParams{ - CheckID: rule.CheckID, - StartTime: time.Now().Add(-time.Duration(rule.ForDuration) * time.Second), - EndTime: time.Now(), - } - metrics, err := a.queries.GetMetricsForCheck(context.Background(), params) - if err != nil { - log.Printf("Error getting metrics for rule %s: %v", rule.Name, err) - return - } - - isFailing := true - for _, metric := range metrics { - if !compare(metric.Value, rule.Threshold, rule.Operator) { - isFailing = false - break - } - } - - if isFailing { - // Create or update incident - } - */ -} - -func compare(value, threshold float64, operator string) bool { - switch operator { - case ">": - return value > threshold - case "<": - return value < threshold - case "=": - return value == threshold - } - return false -} diff --git a/observability-core/internal/collector/collector.go b/observability-core/internal/collector/collector.go deleted file mode 100644 index 8da08b5..0000000 --- a/observability-core/internal/collector/collector.go +++ /dev/null @@ -1,101 +0,0 @@ -package collector - -import ( - "context" - "encoding/json" - "log" - "net/http" - "strconv" - "time" - - "github.com/jackc/pgx/v5/pgtype" - "github.com/lab/observability-core/internal/db" -) - -type Collector struct { - queries *db.Queries -} - -func New(queries *db.Queries) *Collector { - return &Collector{queries: queries} -} - -func (c *Collector) Start() { - log.Println("Starting health collector") - ticker := time.NewTicker(10 * time.Second) // Run every 10 seconds for simplicity - - go func() { - for range ticker.C { - c.runChecks() - } - }() -} - -func (c *Collector) runChecks() { - checks, err := c.queries.ListChecks(context.Background()) - if err != nil { - log.Printf("Error listing checks: %v", err) - return - } - - for _, check := range checks { - go c.performCheck(check) - } -} - -func (c *Collector) performCheck(check db.Check) { - startTime := time.Now() - var value float64 - - target, err := c.queries.GetTarget(context.Background(), check.TargetID) - if err != nil { - log.Printf("Error getting target for check %s: %v", check.ID, err) - return - } - - switch check.Type { - case "http": - var config map[string]interface{} - if err := json.Unmarshal(target.Config, &config); err != nil { - log.Printf("Invalid config for check %s: %v", check.ID, err) - return - } - - // Assume config has a "url" field - url, ok := config["url"].(string) - if !ok { - log.Printf("Invalid URL in config for check %s", check.ID) - return - } - - resp, err := http.Get(url) - if err != nil || resp.StatusCode >= 400 { - value = 0 // Failure - } else { - value = 1 // Success - } - - // Other check types (ping, tcp) would be implemented here - default: - log.Printf("Unknown check type: %s", check.Type) - return - } - - latency := time.Since(startTime).Seconds() - - tags := map[string]string{ - "latency_seconds": strconv.FormatFloat(latency, 'f', -1, 64), - } - tagsJSON, _ := json.Marshal(tags) - - _, err = c.queries.CreateMetric(context.Background(), db.CreateMetricParams{ - Time: pgtype.Timestamptz{Time: startTime, Valid: true}, - CheckID: check.ID, - Value: value, - Tags: tagsJSON, - }) - - if err != nil { - log.Printf("Error creating metric for check %s: %v", check.ID, err) - } -} diff --git a/observability-core/internal/config/config.go b/observability-core/internal/config/config.go deleted file mode 100644 index f1066bd..0000000 --- a/observability-core/internal/config/config.go +++ /dev/null @@ -1,35 +0,0 @@ -package config - -import ( - "log" - "os" - - "github.com/joho/godotenv" -) - -type Config struct { - DatabaseURL string - Port string -} - -func Load() *Config { - err := godotenv.Load() - if err != nil { - log.Println("No .env file found, using environment variables") - } - - return &Config{ - DatabaseURL: getEnv("DATABASE_URL", ""), - Port: getEnv("PORT", "8080"), - } -} - -func getEnv(key, fallback string) string { - if value, ok := os.LookupEnv(key); ok { - return value - } - if fallback == "" { - log.Fatalf("FATAL: Environment variable %s is not set.", key) - } - return fallback -} diff --git a/observability-core/internal/db/db.go b/observability-core/internal/db/db.go deleted file mode 100644 index 9d485b5..0000000 --- a/observability-core/internal/db/db.go +++ /dev/null @@ -1,32 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 - -package db - -import ( - "context" - - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" -) - -type DBTX interface { - Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) - Query(context.Context, string, ...interface{}) (pgx.Rows, error) - QueryRow(context.Context, string, ...interface{}) pgx.Row -} - -func New(db DBTX) *Queries { - return &Queries{db: db} -} - -type Queries struct { - db DBTX -} - -func (q *Queries) WithTx(tx pgx.Tx) *Queries { - return &Queries{ - db: tx, - } -} diff --git a/observability-core/internal/db/models.go b/observability-core/internal/db/models.go deleted file mode 100644 index 121af01..0000000 --- a/observability-core/internal/db/models.go +++ /dev/null @@ -1,232 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 - -package db - -import ( - "database/sql/driver" - "fmt" - - "github.com/jackc/pgx/v5/pgtype" -) - -type AlertSeverity string - -const ( - AlertSeverityInfo AlertSeverity = "info" - AlertSeverityWarning AlertSeverity = "warning" - AlertSeverityCritical AlertSeverity = "critical" -) - -func (e *AlertSeverity) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = AlertSeverity(s) - case string: - *e = AlertSeverity(s) - default: - return fmt.Errorf("unsupported scan type for AlertSeverity: %T", src) - } - return nil -} - -type NullAlertSeverity struct { - AlertSeverity AlertSeverity `json:"alert_severity"` - Valid bool `json:"valid"` // Valid is true if AlertSeverity is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullAlertSeverity) Scan(value interface{}) error { - if value == nil { - ns.AlertSeverity, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.AlertSeverity.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullAlertSeverity) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.AlertSeverity), nil -} - -type CheckType string - -const ( - CheckTypeHttp CheckType = "http" - CheckTypePing CheckType = "ping" - CheckTypeTcp CheckType = "tcp" -) - -func (e *CheckType) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = CheckType(s) - case string: - *e = CheckType(s) - default: - return fmt.Errorf("unsupported scan type for CheckType: %T", src) - } - return nil -} - -type NullCheckType struct { - CheckType CheckType `json:"check_type"` - Valid bool `json:"valid"` // Valid is true if CheckType is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullCheckType) Scan(value interface{}) error { - if value == nil { - ns.CheckType, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.CheckType.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullCheckType) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.CheckType), nil -} - -type IncidentStatus string - -const ( - IncidentStatusOpen IncidentStatus = "open" - IncidentStatusAcknowledged IncidentStatus = "acknowledged" - IncidentStatusResolved IncidentStatus = "resolved" -) - -func (e *IncidentStatus) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = IncidentStatus(s) - case string: - *e = IncidentStatus(s) - default: - return fmt.Errorf("unsupported scan type for IncidentStatus: %T", src) - } - return nil -} - -type NullIncidentStatus struct { - IncidentStatus IncidentStatus `json:"incident_status"` - Valid bool `json:"valid"` // Valid is true if IncidentStatus is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullIncidentStatus) Scan(value interface{}) error { - if value == nil { - ns.IncidentStatus, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.IncidentStatus.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullIncidentStatus) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.IncidentStatus), nil -} - -type TargetType string - -const ( - TargetTypeService TargetType = "service" - TargetTypeInfra TargetType = "infra" -) - -func (e *TargetType) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = TargetType(s) - case string: - *e = TargetType(s) - default: - return fmt.Errorf("unsupported scan type for TargetType: %T", src) - } - return nil -} - -type NullTargetType struct { - TargetType TargetType `json:"target_type"` - Valid bool `json:"valid"` // Valid is true if TargetType is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullTargetType) Scan(value interface{}) error { - if value == nil { - ns.TargetType, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.TargetType.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullTargetType) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.TargetType), nil -} - -type AlertRule struct { - ID pgtype.UUID `json:"id"` - CheckID pgtype.UUID `json:"check_id"` - Name string `json:"name"` - Threshold float64 `json:"threshold"` - Operator string `json:"operator"` - ForDuration int64 `json:"for_duration"` - Severity AlertSeverity `json:"severity"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` -} - -type Check struct { - ID pgtype.UUID `json:"id"` - TargetID pgtype.UUID `json:"target_id"` - Type CheckType `json:"type"` - IntervalSeconds int32 `json:"interval_seconds"` - TimeoutSeconds int32 `json:"timeout_seconds"` - IsActive bool `json:"is_active"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` -} - -type Incident struct { - ID pgtype.UUID `json:"id"` - AlertRuleID pgtype.UUID `json:"alert_rule_id"` - Status IncidentStatus `json:"status"` - StartTime pgtype.Timestamptz `json:"start_time"` - EndTime pgtype.Timestamptz `json:"end_time"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` -} - -type Metric struct { - Time pgtype.Timestamptz `json:"time"` - CheckID pgtype.UUID `json:"check_id"` - Value float64 `json:"value"` - Tags []byte `json:"tags"` -} - -type Target struct { - ID pgtype.UUID `json:"id"` - Name string `json:"name"` - Type TargetType `json:"type"` - Config []byte `json:"config"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` -} diff --git a/observability-core/internal/db/query.sql.go b/observability-core/internal/db/query.sql.go deleted file mode 100644 index 34c1196..0000000 --- a/observability-core/internal/db/query.sql.go +++ /dev/null @@ -1,360 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: query.sql - -package db - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const createAlertRule = `-- name: CreateAlertRule :one -INSERT INTO alert_rules (check_id, name, threshold, operator, for_duration, severity) -VALUES ($1, $2, $3, $4, $5, $6) -RETURNING id, check_id, name, threshold, operator, for_duration, severity, created_at, updated_at -` - -type CreateAlertRuleParams struct { - CheckID pgtype.UUID `json:"check_id"` - Name string `json:"name"` - Threshold float64 `json:"threshold"` - Operator string `json:"operator"` - ForDuration int64 `json:"for_duration"` - Severity AlertSeverity `json:"severity"` -} - -func (q *Queries) CreateAlertRule(ctx context.Context, arg CreateAlertRuleParams) (AlertRule, error) { - row := q.db.QueryRow(ctx, createAlertRule, - arg.CheckID, - arg.Name, - arg.Threshold, - arg.Operator, - arg.ForDuration, - arg.Severity, - ) - var i AlertRule - err := row.Scan( - &i.ID, - &i.CheckID, - &i.Name, - &i.Threshold, - &i.Operator, - &i.ForDuration, - &i.Severity, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const createCheck = `-- name: CreateCheck :one -INSERT INTO checks (target_id, type, interval_seconds, timeout_seconds) -VALUES ($1, $2, $3, $4) -RETURNING id, target_id, type, interval_seconds, timeout_seconds, is_active, created_at, updated_at -` - -type CreateCheckParams struct { - TargetID pgtype.UUID `json:"target_id"` - Type CheckType `json:"type"` - IntervalSeconds int32 `json:"interval_seconds"` - TimeoutSeconds int32 `json:"timeout_seconds"` -} - -func (q *Queries) CreateCheck(ctx context.Context, arg CreateCheckParams) (Check, error) { - row := q.db.QueryRow(ctx, createCheck, - arg.TargetID, - arg.Type, - arg.IntervalSeconds, - arg.TimeoutSeconds, - ) - var i Check - err := row.Scan( - &i.ID, - &i.TargetID, - &i.Type, - &i.IntervalSeconds, - &i.TimeoutSeconds, - &i.IsActive, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const createIncident = `-- name: CreateIncident :one -INSERT INTO incidents (alert_rule_id, status, start_time) -VALUES ($1, $2, $3) -RETURNING id, alert_rule_id, status, start_time, end_time, created_at, updated_at -` - -type CreateIncidentParams struct { - AlertRuleID pgtype.UUID `json:"alert_rule_id"` - Status IncidentStatus `json:"status"` - StartTime pgtype.Timestamptz `json:"start_time"` -} - -func (q *Queries) CreateIncident(ctx context.Context, arg CreateIncidentParams) (Incident, error) { - row := q.db.QueryRow(ctx, createIncident, arg.AlertRuleID, arg.Status, arg.StartTime) - var i Incident - err := row.Scan( - &i.ID, - &i.AlertRuleID, - &i.Status, - &i.StartTime, - &i.EndTime, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const createMetric = `-- name: CreateMetric :one -INSERT INTO metrics (time, check_id, value, tags) -VALUES ($1, $2, $3, $4) -RETURNING time, check_id, value, tags -` - -type CreateMetricParams struct { - Time pgtype.Timestamptz `json:"time"` - CheckID pgtype.UUID `json:"check_id"` - Value float64 `json:"value"` - Tags []byte `json:"tags"` -} - -func (q *Queries) CreateMetric(ctx context.Context, arg CreateMetricParams) (Metric, error) { - row := q.db.QueryRow(ctx, createMetric, - arg.Time, - arg.CheckID, - arg.Value, - arg.Tags, - ) - var i Metric - err := row.Scan( - &i.Time, - &i.CheckID, - &i.Value, - &i.Tags, - ) - return i, err -} - -const createTarget = `-- name: CreateTarget :one -INSERT INTO targets (name, type, config) -VALUES ($1, $2, $3) -RETURNING id, name, type, config, created_at, updated_at -` - -type CreateTargetParams struct { - Name string `json:"name"` - Type TargetType `json:"type"` - Config []byte `json:"config"` -} - -func (q *Queries) CreateTarget(ctx context.Context, arg CreateTargetParams) (Target, error) { - row := q.db.QueryRow(ctx, createTarget, arg.Name, arg.Type, arg.Config) - var i Target - err := row.Scan( - &i.ID, - &i.Name, - &i.Type, - &i.Config, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const getCheck = `-- name: GetCheck :one -SELECT id, target_id, type, interval_seconds, timeout_seconds, is_active, created_at, updated_at FROM checks -WHERE id = $1 -` - -func (q *Queries) GetCheck(ctx context.Context, id pgtype.UUID) (Check, error) { - row := q.db.QueryRow(ctx, getCheck, id) - var i Check - err := row.Scan( - &i.ID, - &i.TargetID, - &i.Type, - &i.IntervalSeconds, - &i.TimeoutSeconds, - &i.IsActive, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const getIncident = `-- name: GetIncident :one -SELECT id, alert_rule_id, status, start_time, end_time, created_at, updated_at FROM incidents -WHERE id = $1 -` - -func (q *Queries) GetIncident(ctx context.Context, id pgtype.UUID) (Incident, error) { - row := q.db.QueryRow(ctx, getIncident, id) - var i Incident - err := row.Scan( - &i.ID, - &i.AlertRuleID, - &i.Status, - &i.StartTime, - &i.EndTime, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const getMetricsForCheck = `-- name: GetMetricsForCheck :many -SELECT time, check_id, value, tags FROM metrics -WHERE check_id = $1 AND time >= $2 AND time <= $3 -ORDER BY time DESC -` - -type GetMetricsForCheckParams struct { - CheckID pgtype.UUID `json:"check_id"` - Time pgtype.Timestamptz `json:"time"` - Time_2 pgtype.Timestamptz `json:"time_2"` -} - -func (q *Queries) GetMetricsForCheck(ctx context.Context, arg GetMetricsForCheckParams) ([]Metric, error) { - rows, err := q.db.Query(ctx, getMetricsForCheck, arg.CheckID, arg.Time, arg.Time_2) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Metric - for rows.Next() { - var i Metric - if err := rows.Scan( - &i.Time, - &i.CheckID, - &i.Value, - &i.Tags, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getTarget = `-- name: GetTarget :one -SELECT id, name, type, config, created_at, updated_at FROM targets -WHERE id = $1 -` - -func (q *Queries) GetTarget(ctx context.Context, id pgtype.UUID) (Target, error) { - row := q.db.QueryRow(ctx, getTarget, id) - var i Target - err := row.Scan( - &i.ID, - &i.Name, - &i.Type, - &i.Config, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const listAlertRules = `-- name: ListAlertRules :many -SELECT id, check_id, name, threshold, operator, for_duration, severity, created_at, updated_at FROM alert_rules -` - -func (q *Queries) ListAlertRules(ctx context.Context) ([]AlertRule, error) { - rows, err := q.db.Query(ctx, listAlertRules) - if err != nil { - return nil, err - } - defer rows.Close() - var items []AlertRule - for rows.Next() { - var i AlertRule - if err := rows.Scan( - &i.ID, - &i.CheckID, - &i.Name, - &i.Threshold, - &i.Operator, - &i.ForDuration, - &i.Severity, - &i.CreatedAt, - &i.UpdatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const listChecks = `-- name: ListChecks :many -SELECT id, target_id, type, interval_seconds, timeout_seconds, is_active, created_at, updated_at FROM checks -WHERE is_active = TRUE -` - -func (q *Queries) ListChecks(ctx context.Context) ([]Check, error) { - rows, err := q.db.Query(ctx, listChecks) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Check - for rows.Next() { - var i Check - if err := rows.Scan( - &i.ID, - &i.TargetID, - &i.Type, - &i.IntervalSeconds, - &i.TimeoutSeconds, - &i.IsActive, - &i.CreatedAt, - &i.UpdatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const updateIncidentStatus = `-- name: UpdateIncidentStatus :one -UPDATE incidents -SET status = $2, updated_at = NOW() -WHERE id = $1 -RETURNING id, alert_rule_id, status, start_time, end_time, created_at, updated_at -` - -type UpdateIncidentStatusParams struct { - ID pgtype.UUID `json:"id"` - Status IncidentStatus `json:"status"` -} - -func (q *Queries) UpdateIncidentStatus(ctx context.Context, arg UpdateIncidentStatusParams) (Incident, error) { - row := q.db.QueryRow(ctx, updateIncidentStatus, arg.ID, arg.Status) - var i Incident - err := row.Scan( - &i.ID, - &i.AlertRuleID, - &i.Status, - &i.StartTime, - &i.EndTime, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} diff --git a/observability-core/migrations/000001_init_schema.up.sql b/observability-core/migrations/000001_init_schema.up.sql deleted file mode 100644 index 4ab4c17..0000000 --- a/observability-core/migrations/000001_init_schema.up.sql +++ /dev/null @@ -1,80 +0,0 @@ --- +migrate Up -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -CREATE EXTENSION IF NOT EXISTS timescaledb; - -CREATE TYPE target_type AS ENUM ('service', 'infra'); -CREATE TYPE check_type AS ENUM ('http', 'ping', 'tcp'); -CREATE TYPE incident_status AS ENUM ('open', 'acknowledged', 'resolved'); -CREATE TYPE alert_severity AS ENUM ('info', 'warning', 'critical'); - -CREATE TABLE targets ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - name VARCHAR(255) NOT NULL, - type target_type NOT NULL, - config JSONB NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE TABLE checks ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - target_id UUID NOT NULL REFERENCES targets(id) ON DELETE CASCADE, - type check_type NOT NULL, - interval_seconds INTEGER NOT NULL, - timeout_seconds INTEGER NOT NULL, - is_active BOOLEAN NOT NULL DEFAULT TRUE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE TABLE metrics ( - time TIMESTAMPTZ NOT NULL, - check_id UUID NOT NULL REFERENCES checks(id) ON DELETE CASCADE, - value DOUBLE PRECISION NOT NULL, - tags JSONB -); - --- Create a hypertable for metrics -SELECT create_hypertable('metrics', 'time'); - -CREATE TABLE alert_rules ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - check_id UUID NOT NULL REFERENCES checks(id) ON DELETE CASCADE, - name VARCHAR(255) NOT NULL, - threshold DOUBLE PRECISION NOT NULL, - -- e.g., '>', '<', '=' - operator VARCHAR(10) NOT NULL, - -- in seconds - for_duration BIGINT NOT NULL, - severity alert_severity NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE TABLE incidents ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - alert_rule_id UUID NOT NULL REFERENCES alert_rules(id) ON DELETE CASCADE, - status incident_status NOT NULL, - start_time TIMESTAMPTZ NOT NULL, - end_time TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - --- Indexes -CREATE INDEX ON checks (target_id); -CREATE INDEX ON metrics (check_id, time DESC); -CREATE INDEX ON alert_rules (check_id); -CREATE INDEX ON incidents (alert_rule_id, status); - - --- +migrate Down -DROP TABLE IF EXISTS incidents; -DROP TABLE IF EXISTS alert_rules; -DROP TABLE IF EXISTS metrics; -DROP TABLE IF EXISTS checks; -DROP TABLE IF EXISTS targets; -DROP TYPE IF EXISTS incident_status; -DROP TYPE IF EXISTS alert_severity; -DROP TYPE IF EXISTS check_type; -DROP TYPE IF EXISTS target_type; diff --git a/observability-core/sqlc.yaml b/observability-core/sqlc.yaml deleted file mode 100644 index d719475..0000000 --- a/observability-core/sqlc.yaml +++ /dev/null @@ -1,11 +0,0 @@ -version: "2" -sql: - - engine: "postgresql" - queries: "db/query.sql" - schema: "migrations" - gen: - go: - package: "db" - out: "internal/db" - sql_package: "pgx/v5" - emit_json_tags: true diff --git a/platform-projects-core/.env.example b/platform-projects-core/.env.example deleted file mode 100644 index 63d7f98..0000000 --- a/platform-projects-core/.env.example +++ /dev/null @@ -1,10 +0,0 @@ -SERVICE_NAME=platform-projects-core -ENVIRONMENT=development -HTTP_ADDRESS=:8080 -DATABASE_DSN=postgres://postgres:postgres@localhost:5432/platform_projects?sslmode=disable -AUTH_JWKS_URL= -AUTH_FALLBACK_SECRET=dev-secret -AUTH_AUDIENCE=platform -AUTH_ISSUER=platform-core -AUTH_CLOCK_SKEW_SECONDS=60 - diff --git a/platform-projects-core/Dockerfile b/platform-projects-core/Dockerfile deleted file mode 100644 index 689315a..0000000 --- a/platform-projects-core/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM docker.io/library/golang:1.22-alpine AS builder -WORKDIR /app -RUN apk add --no-cache git -COPY go.mod go.sum ./ -RUN go mod download -COPY . ./ -RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/platform-projects-core ./cmd/api - -FROM alpine:3.19 -RUN apk add --no-cache ca-certificates -WORKDIR /app -COPY --from=builder /bin/platform-projects-core /app/platform-projects-core -EXPOSE 8080 -HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD wget -qO- http://localhost:8080/api/v1/health || exit 1 -ENTRYPOINT ["/app/platform-projects-core"] - diff --git a/platform-projects-core/Makefile b/platform-projects-core/Makefile deleted file mode 100644 index 6a85188..0000000 --- a/platform-projects-core/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -APP_NAME=platform-projects-core - -.PHONY: run -run: - go run ./cmd/api - -.PHONY: lint -lint: - golangci-lint run ./... - -.PHONY: test -test: - go test ./... - -.PHONY: migrate -migrate: - migrate -path internal/db/migrations -database "$${DATABASE_DSN}" up - -.PHONY: sqlc -sqlc: - sqlc generate -f internal/db/sqlc/sqlc.yaml - diff --git a/platform-projects-core/README.md b/platform-projects-core/README.md deleted file mode 100644 index 8883b8d..0000000 --- a/platform-projects-core/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# platform-projects-core - -`platform-projects-core` é o ERP técnico da plataforma. Ele é a **fonte de verdade** sobre projetos, produtos e ambientes. - -## Responsabilidade e limites - -**Este serviço faz:** -- Governança de projetos, ambientes, repositórios, tarefas e vínculos externos. -- Orquestração de metadados com multi-tenancy por design. -- Auditoria e observabilidade para uso enterprise. - -**Este serviço não faz:** -- Não executa código de cliente. -- Não faz CI/CD ou deploy. -- Não autentica usuários finais. -- Não substitui GitHub, GitLab ou clouds. - -## Fluxo de dados - -1. Requisições autenticadas chegam em `/api/v1`. -2. Middleware JWT valida, injeta `tenantId` no contexto e bloqueia requisições inválidas. -3. Handlers transformam DTOs em comandos e chamam os casos de uso. -4. Casos de uso aplicam regras de domínio e persistem via repositórios. -5. Respostas padronizadas são retornadas ao cliente. - -## Modelo de domínio (resumo) - -- **Project (Aggregate Root)**: slug único por tenant, status controlado. -- **Environment**: pertence a um projeto, tipos controlados. -- **Repository**: metadados, provider enum, URL validada, branch obrigatória. -- **Project Links**: somente IDs externos, meta JSON sem segredo. -- **Tasks**: tarefas com status, prazo e vínculo por projeto. - -## Exemplo de uso - -Criar projeto: - -```bash -curl -X POST http://localhost:8080/api/v1/projects \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - -d '{"name":"Core ERP","slug":"core-erp","description":"ERP técnico"}' -``` - -Criar ambiente: - -```bash -curl -X POST http://localhost:8080/api/v1/environments \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - -d '{"project_id":"","type":"production"}' -``` - -Criar tarefa: - -```bash -curl -X POST http://localhost:8080/api/v1/tasks \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - -d '{"project_id":"","title":"Kickoff","description":"Reunião inicial","due_date":"2024-09-01T12:00:00Z"}' -``` - -## Escalabilidade com segurança - -- Multi-tenancy explícito em todas as tabelas e queries. -- JWT validado por JWKS e fallback opcional por secret interno. -- Logs estruturados e middlewares defensivos. -- Configuração validada no boot (fail-fast). - -## Comandos - -```bash -make run -make migrate -make sqlc -make test -make lint -``` - -## Observabilidade - -- Logs estruturados via `slog`. -- Endpoint `/api/v1/health`. -- Endpoint `/api/v1/metrics` (placeholder para instrumentação enterprise). diff --git a/platform-projects-core/cmd/api/main.go b/platform-projects-core/cmd/api/main.go deleted file mode 100644 index ab48baa..0000000 --- a/platform-projects-core/cmd/api/main.go +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "context" - "log/slog" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - apihttp "platform-projects-core/internal/api/http" - "platform-projects-core/internal/config" - "platform-projects-core/internal/infrastructure/auth" - "platform-projects-core/internal/infrastructure/postgres" - "platform-projects-core/internal/observability" -) - -func main() { - ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - defer stop() - - cfg, err := config.Load() - if err != nil { - slog.Error("config error", "error", err) - os.Exit(1) - } - - logger := observability.NewLogger(cfg.Environment) - logger.Info("starting service", "service", cfg.ServiceName) - - pool, err := postgres.NewPool(ctx, cfg.Database) - if err != nil { - logger.Error("database connection failed", "error", err) - os.Exit(1) - } - defer pool.Close() - - jwtValidator, err := auth.NewValidator(ctx, cfg.Auth) - if err != nil { - logger.Error("jwt validator init failed", "error", err) - os.Exit(1) - } - - router := apihttp.NewRouter(apihttp.RouterDependencies{ - Logger: logger, - DB: pool, - JWTValidator: jwtValidator, - Config: cfg, - }) - - srv := &http.Server{ - Addr: cfg.HTTP.Address, - Handler: router, - ReadHeaderTimeout: 5 * time.Second, - } - - go func() { - <-ctx.Done() - shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - if err := srv.Shutdown(shutdownCtx); err != nil { - logger.Error("http shutdown error", "error", err) - } - }() - - logger.Info("http server listening", "addr", cfg.HTTP.Address) - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - logger.Error("http server failed", "error", err) - os.Exit(1) - } - - logger.Info("service stopped") -} diff --git a/platform-projects-core/docker-compose.yml b/platform-projects-core/docker-compose.yml deleted file mode 100644 index 25004a1..0000000 --- a/platform-projects-core/docker-compose.yml +++ /dev/null @@ -1,30 +0,0 @@ -version: "3.9" -services: - api: - build: . - environment: - SERVICE_NAME: platform-projects-core - ENVIRONMENT: development - HTTP_ADDRESS: ":8080" - DATABASE_DSN: postgres://postgres:postgres@db:5432/platform_projects?sslmode=disable - AUTH_JWKS_URL: "" - AUTH_FALLBACK_SECRET: "dev-secret" - AUTH_AUDIENCE: "platform" - AUTH_ISSUER: "platform-core" - ports: - - "8080:8080" - depends_on: - - db - db: - image: postgres:16 - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: platform_projects - ports: - - "5432:5432" - volumes: - - pgdata:/var/lib/postgresql/data -volumes: - pgdata: - diff --git a/platform-projects-core/docs/architecture.md b/platform-projects-core/docs/architecture.md deleted file mode 100644 index ad7f07b..0000000 --- a/platform-projects-core/docs/architecture.md +++ /dev/null @@ -1,21 +0,0 @@ -# Arquitetura - -## Camadas - -- **API Layer**: HTTP, validação, DTOs, respostas padronizadas. -- **Application**: casos de uso e contratos explícitos. -- **Domain**: entidades e regras de negócio. -- **Infrastructure**: adaptadores para banco, JWT e observabilidade. - -## Contratos explícitos - -- Use cases dependem de interfaces de repositório. -- Handlers não conhecem SQL. -- Infraestrutura não conhece HTTP. - -## Multi-tenancy - -- `tenant_id` obrigatório em todas as tabelas. -- Todas as queries exigem `tenant_id`. -- `tenantId` é injetado no contexto durante autenticação. - diff --git a/platform-projects-core/docs/domain-model.md b/platform-projects-core/docs/domain-model.md deleted file mode 100644 index cd24495..0000000 --- a/platform-projects-core/docs/domain-model.md +++ /dev/null @@ -1,48 +0,0 @@ -# Modelo de domínio - -## Project (Aggregate Root) - -- `id` -- `tenant_id` -- `name` -- `slug` -- `description` -- `status` (active, paused, archived) -- `created_at` -- `updated_at` - -**Regras:** -- `slug` é único por tenant. -- Projeto arquivado não aceita novos vínculos. - -## Environment - -- `id` -- `tenant_id` -- `project_id` -- `type` -- `paused` - -## Repository - -- `id` -- `tenant_id` -- `project_id` -- `provider` -- `url` -- `default_branch` - -## Task - -- `id` -- `tenant_id` -- `project_id` -- `title` -- `description` -- `status` (open, in_progress, done, archived) -- `due_date` - -## Project Links - -- `infra`, `billing`, `security` -- apenas IDs externos e `meta` JSON sem segredo diff --git a/platform-projects-core/docs/security-model.md b/platform-projects-core/docs/security-model.md deleted file mode 100644 index 6faf399..0000000 --- a/platform-projects-core/docs/security-model.md +++ /dev/null @@ -1,14 +0,0 @@ -# Modelo de segurança - -## Autenticação - -- JWT validado via JWKS (padrão). -- Fallback opcional para secret interno (MVP). -- Claims obrigatórias: `sub`, `tenantId`, `roles`. - -## Autorização - -- `tenantId` injetado no contexto. -- Toda query utiliza `tenant_id` explicitamente. -- Falha de segurança gera erro explícito para o cliente. - diff --git a/platform-projects-core/go.mod b/platform-projects-core/go.mod deleted file mode 100644 index d5c4e6c..0000000 --- a/platform-projects-core/go.mod +++ /dev/null @@ -1,20 +0,0 @@ -module platform-projects-core - -go 1.22 - -require ( - github.com/MicahParks/keyfunc/v2 v2.1.0 - github.com/go-chi/chi/v5 v5.0.10 - github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.6.0 -) - -require ( - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/text v0.14.0 // indirect -) diff --git a/platform-projects-core/go.sum b/platform-projects-core/go.sum deleted file mode 100644 index c98020c..0000000 --- a/platform-projects-core/go.sum +++ /dev/null @@ -1,36 +0,0 @@ -github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k= -github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= -github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= -github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/platform-projects-core/internal/api/handlers/environments_handler.go b/platform-projects-core/internal/api/handlers/environments_handler.go deleted file mode 100644 index 2e690e1..0000000 --- a/platform-projects-core/internal/api/handlers/environments_handler.go +++ /dev/null @@ -1,57 +0,0 @@ -package handlers - -import ( - "encoding/json" - "log/slog" - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/jackc/pgx/v5/pgxpool" - "platform-projects-core/internal/api/transport" - "platform-projects-core/internal/application/environments" - "platform-projects-core/internal/infrastructure/repositories" -) - -type EnvironmentsHandler struct { - logger *slog.Logger - usecase environments.CreateEnvironmentUseCase -} - -func NewEnvironmentsHandler(logger *slog.Logger, pool *pgxpool.Pool) http.Handler { - repo := repositories.NewEnvironmentsRepository(pool) - h := &EnvironmentsHandler{ - logger: logger, - usecase: environments.CreateEnvironmentUseCase{Repo: repo}, - } - r := chi.NewRouter() - r.Post("/", h.create) - return r -} - -type createEnvironmentRequest struct { - ProjectID string `json:"project_id"` - Type string `json:"type"` -} - -func (h *EnvironmentsHandler) create(w http.ResponseWriter, r *http.Request) { - tenantID, ok := transport.TenantFromContext(r.Context()) - if !ok { - transport.WriteError(w, http.StatusUnauthorized, "unauthorized", "tenant missing") - return - } - var req createEnvironmentRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - transport.WriteError(w, http.StatusBadRequest, "invalid_payload", "invalid JSON payload") - return - } - env, err := h.usecase.Execute(r.Context(), environments.CreateEnvironmentInput{ - TenantID: tenantID, - ProjectID: req.ProjectID, - Type: req.Type, - }) - if err != nil { - writeDomainError(w, err) - return - } - transport.WriteJSON(w, http.StatusCreated, transport.Envelope{"environment": env}) -} diff --git a/platform-projects-core/internal/api/handlers/project_links_handler.go b/platform-projects-core/internal/api/handlers/project_links_handler.go deleted file mode 100644 index 81ccb39..0000000 --- a/platform-projects-core/internal/api/handlers/project_links_handler.go +++ /dev/null @@ -1,113 +0,0 @@ -package handlers - -import ( - "encoding/json" - "log/slog" - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/jackc/pgx/v5/pgxpool" - "platform-projects-core/internal/api/transport" - "platform-projects-core/internal/application/project_links" - "platform-projects-core/internal/infrastructure/repositories" -) - -type ProjectLinksHandler struct { - logger *slog.Logger - infra project_links.LinkInfraUseCase - billing project_links.LinkBillingUseCase - security project_links.LinkSecurityUseCase -} - -func NewProjectLinksHandler(logger *slog.Logger, pool *pgxpool.Pool) http.Handler { - repo := repositories.NewProjectLinksRepository(pool) - h := &ProjectLinksHandler{ - logger: logger, - infra: project_links.LinkInfraUseCase{Repo: repo}, - billing: project_links.LinkBillingUseCase{Repo: repo}, - security: project_links.LinkSecurityUseCase{Repo: repo}, - } - r := chi.NewRouter() - r.Post("/infra", h.linkInfra) - r.Post("/billing", h.linkBilling) - r.Post("/security", h.linkSecurity) - return r -} - -type linkRequest struct { - ProjectID string `json:"project_id"` - External string `json:"external"` - Meta map[string]string `json:"meta"` -} - -func (h *ProjectLinksHandler) linkInfra(w http.ResponseWriter, r *http.Request) { - tenantID, ok := transport.TenantFromContext(r.Context()) - if !ok { - transport.WriteError(w, http.StatusUnauthorized, "unauthorized", "tenant missing") - return - } - var req linkRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - transport.WriteError(w, http.StatusBadRequest, "invalid_payload", "invalid JSON payload") - return - } - link, err := h.infra.Execute(r.Context(), project_links.LinkInfraInput{ - TenantID: tenantID, - ProjectID: req.ProjectID, - External: req.External, - Meta: req.Meta, - }) - if err != nil { - writeDomainError(w, err) - return - } - transport.WriteJSON(w, http.StatusCreated, transport.Envelope{"link": link}) -} - -func (h *ProjectLinksHandler) linkBilling(w http.ResponseWriter, r *http.Request) { - tenantID, ok := transport.TenantFromContext(r.Context()) - if !ok { - transport.WriteError(w, http.StatusUnauthorized, "unauthorized", "tenant missing") - return - } - var req linkRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - transport.WriteError(w, http.StatusBadRequest, "invalid_payload", "invalid JSON payload") - return - } - link, err := h.billing.Execute(r.Context(), project_links.LinkBillingInput{ - TenantID: tenantID, - ProjectID: req.ProjectID, - External: req.External, - Meta: req.Meta, - }) - if err != nil { - writeDomainError(w, err) - return - } - transport.WriteJSON(w, http.StatusCreated, transport.Envelope{"link": link}) -} - -func (h *ProjectLinksHandler) linkSecurity(w http.ResponseWriter, r *http.Request) { - tenantID, ok := transport.TenantFromContext(r.Context()) - if !ok { - transport.WriteError(w, http.StatusUnauthorized, "unauthorized", "tenant missing") - return - } - var req linkRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - transport.WriteError(w, http.StatusBadRequest, "invalid_payload", "invalid JSON payload") - return - } - link, err := h.security.Execute(r.Context(), project_links.LinkSecurityInput{ - TenantID: tenantID, - ProjectID: req.ProjectID, - External: req.External, - Meta: req.Meta, - }) - if err != nil { - writeDomainError(w, err) - return - } - transport.WriteJSON(w, http.StatusCreated, transport.Envelope{"link": link}) -} diff --git a/platform-projects-core/internal/api/handlers/projects_handler.go b/platform-projects-core/internal/api/handlers/projects_handler.go deleted file mode 100644 index b3188f0..0000000 --- a/platform-projects-core/internal/api/handlers/projects_handler.go +++ /dev/null @@ -1,147 +0,0 @@ -package handlers - -import ( - "encoding/json" - "log/slog" - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/jackc/pgx/v5/pgxpool" - "platform-projects-core/internal/api/transport" - appprojects "platform-projects-core/internal/application/projects" - "platform-projects-core/internal/domain/common" - domainprojects "platform-projects-core/internal/domain/projects" - "platform-projects-core/internal/infrastructure/repositories" -) - -type ProjectsHandler struct { - logger *slog.Logger - usecases struct { - create appprojects.CreateProjectUseCase - update appprojects.UpdateProjectUseCase - archive appprojects.ArchiveProjectUseCase - list appprojects.ListProjectsUseCase - } -} - -func NewProjectsHandler(logger *slog.Logger, pool *pgxpool.Pool) http.Handler { - repo := repositories.NewProjectsRepository(pool) - h := &ProjectsHandler{logger: logger} - h.usecases.create = appprojects.CreateProjectUseCase{Repo: repo} - h.usecases.update = appprojects.UpdateProjectUseCase{Repo: repo} - h.usecases.archive = appprojects.ArchiveProjectUseCase{Repo: repo} - h.usecases.list = appprojects.ListProjectsUseCase{Repo: repo} - - r := chi.NewRouter() - r.Post("/", h.create) - r.Get("/", h.list) - r.Put("/{projectId}", h.update) - r.Delete("/{projectId}", h.archive) - return r -} - -type createProjectRequest struct { - Name string `json:"name"` - Slug string `json:"slug"` - Description string `json:"description"` -} - -func (h *ProjectsHandler) create(w http.ResponseWriter, r *http.Request) { - tenantID, ok := transport.TenantFromContext(r.Context()) - if !ok { - transport.WriteError(w, http.StatusUnauthorized, "unauthorized", "tenant missing") - return - } - var req createProjectRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - transport.WriteError(w, http.StatusBadRequest, "invalid_payload", "invalid JSON payload") - return - } - project, err := h.usecases.create.Execute(r.Context(), appprojects.CreateProjectInput{ - TenantID: tenantID, - Name: req.Name, - Slug: req.Slug, - Description: req.Description, - }) - if err != nil { - writeDomainError(w, err) - return - } - transport.WriteJSON(w, http.StatusCreated, transport.Envelope{"project": project}) -} - -type updateProjectRequest struct { - Name string `json:"name"` - Description string `json:"description"` - Status string `json:"status"` -} - -func (h *ProjectsHandler) update(w http.ResponseWriter, r *http.Request) { - tenantID, ok := transport.TenantFromContext(r.Context()) - if !ok { - transport.WriteError(w, http.StatusUnauthorized, "unauthorized", "tenant missing") - return - } - projectID := chi.URLParam(r, "projectId") - var req updateProjectRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - transport.WriteError(w, http.StatusBadRequest, "invalid_payload", "invalid JSON payload") - return - } - project, err := h.usecases.update.Execute(r.Context(), appprojects.UpdateProjectInput{ - TenantID: tenantID, - ProjectID: projectID, - Name: req.Name, - Description: req.Description, - Status: domainprojects.Status(req.Status), - }) - if err != nil { - writeDomainError(w, err) - return - } - transport.WriteJSON(w, http.StatusOK, transport.Envelope{"project": project}) -} - -func (h *ProjectsHandler) archive(w http.ResponseWriter, r *http.Request) { - tenantID, ok := transport.TenantFromContext(r.Context()) - if !ok { - transport.WriteError(w, http.StatusUnauthorized, "unauthorized", "tenant missing") - return - } - projectID := chi.URLParam(r, "projectId") - if err := h.usecases.archive.Execute(r.Context(), appprojects.ArchiveProjectInput{ - TenantID: tenantID, - ProjectID: projectID, - }); err != nil { - writeDomainError(w, err) - return - } - transport.WriteJSON(w, http.StatusOK, transport.Envelope{"status": "archived"}) -} - -func (h *ProjectsHandler) list(w http.ResponseWriter, r *http.Request) { - tenantID, ok := transport.TenantFromContext(r.Context()) - if !ok { - transport.WriteError(w, http.StatusUnauthorized, "unauthorized", "tenant missing") - return - } - projectsList, err := h.usecases.list.Execute(r.Context(), appprojects.ListProjectsInput{TenantID: tenantID}) - if err != nil { - writeDomainError(w, err) - return - } - transport.WriteJSON(w, http.StatusOK, transport.Envelope{"projects": projectsList}) -} - -func writeDomainError(w http.ResponseWriter, err error) { - switch err { - case common.ErrInvalidInput: - transport.WriteError(w, http.StatusBadRequest, "invalid_input", "invalid input") - case common.ErrNotFound: - transport.WriteError(w, http.StatusNotFound, "not_found", "resource not found") - case common.ErrConflict: - transport.WriteError(w, http.StatusConflict, "conflict", "conflict") - default: - transport.WriteError(w, http.StatusInternalServerError, "internal_error", "unexpected error") - } -} diff --git a/platform-projects-core/internal/api/handlers/repositories_handler.go b/platform-projects-core/internal/api/handlers/repositories_handler.go deleted file mode 100644 index 5e4d9be..0000000 --- a/platform-projects-core/internal/api/handlers/repositories_handler.go +++ /dev/null @@ -1,61 +0,0 @@ -package handlers - -import ( - "encoding/json" - "log/slog" - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/jackc/pgx/v5/pgxpool" - "platform-projects-core/internal/api/transport" - apprepos "platform-projects-core/internal/application/repositories" - infrarepos "platform-projects-core/internal/infrastructure/repositories" -) - -type RepositoriesHandler struct { - logger *slog.Logger - usecase apprepos.LinkRepositoryUseCase -} - -func NewRepositoriesHandler(logger *slog.Logger, pool *pgxpool.Pool) http.Handler { - repo := infrarepos.NewRepositoriesRepository(pool) - h := &RepositoriesHandler{ - logger: logger, - usecase: apprepos.LinkRepositoryUseCase{Repo: repo}, - } - r := chi.NewRouter() - r.Post("/", h.link) - return r -} - -type linkRepositoryRequest struct { - ProjectID string `json:"project_id"` - Provider string `json:"provider"` - URL string `json:"url"` - DefaultBranch string `json:"default_branch"` -} - -func (h *RepositoriesHandler) link(w http.ResponseWriter, r *http.Request) { - tenantID, ok := transport.TenantFromContext(r.Context()) - if !ok { - transport.WriteError(w, http.StatusUnauthorized, "unauthorized", "tenant missing") - return - } - var req linkRepositoryRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - transport.WriteError(w, http.StatusBadRequest, "invalid_payload", "invalid JSON payload") - return - } - repo, err := h.usecase.Execute(r.Context(), apprepos.LinkRepositoryInput{ - TenantID: tenantID, - ProjectID: req.ProjectID, - Provider: req.Provider, - URL: req.URL, - DefaultBranch: req.DefaultBranch, - }) - if err != nil { - writeDomainError(w, err) - return - } - transport.WriteJSON(w, http.StatusCreated, transport.Envelope{"repository": repo}) -} diff --git a/platform-projects-core/internal/api/handlers/teams_handler.go b/platform-projects-core/internal/api/handlers/teams_handler.go deleted file mode 100644 index dd5b845..0000000 --- a/platform-projects-core/internal/api/handlers/teams_handler.go +++ /dev/null @@ -1,57 +0,0 @@ -package handlers - -import ( - "encoding/json" - "log/slog" - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/jackc/pgx/v5/pgxpool" - "platform-projects-core/internal/api/transport" - "platform-projects-core/internal/application/teams" - "platform-projects-core/internal/infrastructure/repositories" -) - -type TeamsHandler struct { - logger *slog.Logger - usecase teams.ManageTeamUseCase -} - -func NewTeamsHandler(logger *slog.Logger, pool *pgxpool.Pool) http.Handler { - repo := repositories.NewTeamsRepository(pool) - h := &TeamsHandler{ - logger: logger, - usecase: teams.ManageTeamUseCase{Repo: repo}, - } - r := chi.NewRouter() - r.Post("/", h.upsert) - return r -} - -type manageTeamRequest struct { - ProjectID string `json:"project_id"` - Name string `json:"name"` -} - -func (h *TeamsHandler) upsert(w http.ResponseWriter, r *http.Request) { - tenantID, ok := transport.TenantFromContext(r.Context()) - if !ok { - transport.WriteError(w, http.StatusUnauthorized, "unauthorized", "tenant missing") - return - } - var req manageTeamRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - transport.WriteError(w, http.StatusBadRequest, "invalid_payload", "invalid JSON payload") - return - } - team, err := h.usecase.Execute(r.Context(), teams.ManageTeamInput{ - TenantID: tenantID, - ProjectID: req.ProjectID, - Name: req.Name, - }) - if err != nil { - writeDomainError(w, err) - return - } - transport.WriteJSON(w, http.StatusCreated, transport.Envelope{"team": team}) -} diff --git a/platform-projects-core/internal/api/http/middleware.go b/platform-projects-core/internal/api/http/middleware.go deleted file mode 100644 index 0920f80..0000000 --- a/platform-projects-core/internal/api/http/middleware.go +++ /dev/null @@ -1,36 +0,0 @@ -package http - -import ( - "log/slog" - "net/http" - "time" - - "platform-projects-core/internal/api/transport" -) - -func RequestLogger(logger *slog.Logger) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - ww := transport.WrapResponseWriter(w) - next.ServeHTTP(ww, r) - logger.Info("http request", - "method", r.Method, - "path", r.URL.Path, - "status", transport.StatusCode(ww), - "duration_ms", time.Since(start).Milliseconds(), - ) - }) - } -} - -func SecurityHeaders() func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("X-Content-Type-Options", "nosniff") - w.Header().Set("X-Frame-Options", "DENY") - w.Header().Set("Referrer-Policy", "no-referrer") - next.ServeHTTP(w, r) - }) - } -} diff --git a/platform-projects-core/internal/api/http/router.go b/platform-projects-core/internal/api/http/router.go deleted file mode 100644 index 6d09f04..0000000 --- a/platform-projects-core/internal/api/http/router.go +++ /dev/null @@ -1,53 +0,0 @@ -package http - -import ( - "log/slog" - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "github.com/jackc/pgx/v5/pgxpool" - "platform-projects-core/internal/api/handlers" - "platform-projects-core/internal/api/transport" - "platform-projects-core/internal/config" - "platform-projects-core/internal/infrastructure/auth" - "platform-projects-core/internal/observability" -) - -type RouterDependencies struct { - Logger *slog.Logger - DB *pgxpool.Pool - JWTValidator *auth.Validator - Config config.Config -} - -func NewRouter(deps RouterDependencies) http.Handler { - r := chi.NewRouter() - logger := deps.Logger - - r.Use(middleware.RequestID) - r.Use(middleware.RealIP) - r.Use(middleware.Recoverer) - r.Use(RequestLogger(logger)) - r.Use(SecurityHeaders()) - - r.Route("/api/v1", func(r chi.Router) { - r.Get("/health", func(w http.ResponseWriter, r *http.Request) { - transport.WriteJSON(w, http.StatusOK, transport.Envelope{"status": "ok"}) - }) - r.Get("/metrics", observability.MetricsHandler().ServeHTTP) - - r.Group(func(r chi.Router) { - r.Use(auth.Middleware(deps.JWTValidator)) - - r.Mount("/projects", handlers.NewProjectsHandler(logger, deps.DB)) - r.Mount("/environments", handlers.NewEnvironmentsHandler(logger, deps.DB)) - r.Mount("/repositories", handlers.NewRepositoriesHandler(logger, deps.DB)) - r.Mount("/teams", handlers.NewTeamsHandler(logger, deps.DB)) - r.Mount("/tasks", handlers.NewTasksHandler(logger, deps.DB)) - r.Mount("/project-links", handlers.NewProjectLinksHandler(logger, deps.DB)) - }) - }) - - return r -} diff --git a/platform-projects-core/internal/api/transport/context.go b/platform-projects-core/internal/api/transport/context.go deleted file mode 100644 index 0f00c69..0000000 --- a/platform-projects-core/internal/api/transport/context.go +++ /dev/null @@ -1,16 +0,0 @@ -package transport - -import "context" - -type contextKey string - -const tenantKey contextKey = "tenant_id" - -func WithTenant(ctx context.Context, tenantID string) context.Context { - return context.WithValue(ctx, tenantKey, tenantID) -} - -func TenantFromContext(ctx context.Context) (string, bool) { - tenant, ok := ctx.Value(tenantKey).(string) - return tenant, ok -} diff --git a/platform-projects-core/internal/api/transport/response.go b/platform-projects-core/internal/api/transport/response.go deleted file mode 100644 index 6504b08..0000000 --- a/platform-projects-core/internal/api/transport/response.go +++ /dev/null @@ -1,41 +0,0 @@ -package transport - -import ( - "encoding/json" - "net/http" -) - -type Envelope map[string]any - -type ErrorResponse struct { - Message string `json:"message"` - Code string `json:"code"` -} - -type responseWriter struct { - http.ResponseWriter - statusCode int -} - -func (w *responseWriter) WriteHeader(statusCode int) { - w.statusCode = statusCode - w.ResponseWriter.WriteHeader(statusCode) -} - -func WriteJSON(w http.ResponseWriter, status int, payload any) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(payload) -} - -func WriteError(w http.ResponseWriter, status int, code, message string) { - WriteJSON(w, status, Envelope{"error": ErrorResponse{Message: message, Code: code}}) -} - -func WrapResponseWriter(w http.ResponseWriter) *responseWriter { - return &responseWriter{ResponseWriter: w, statusCode: http.StatusOK} -} - -func StatusCode(w *responseWriter) int { - return w.statusCode -} diff --git a/platform-projects-core/internal/application/environments/create_environment.go b/platform-projects-core/internal/application/environments/create_environment.go deleted file mode 100644 index bd8566e..0000000 --- a/platform-projects-core/internal/application/environments/create_environment.go +++ /dev/null @@ -1,29 +0,0 @@ -package environments - -import ( - "context" - - "platform-projects-core/internal/domain/projects" -) - -type EnvironmentRepository interface { - Create(ctx context.Context, env projects.Environment) (projects.Environment, error) -} - -type CreateEnvironmentUseCase struct { - Repo EnvironmentRepository -} - -type CreateEnvironmentInput struct { - TenantID string - ProjectID string - Type string -} - -func (uc CreateEnvironmentUseCase) Execute(ctx context.Context, input CreateEnvironmentInput) (projects.Environment, error) { - env, err := projects.NewEnvironment(input.TenantID, input.ProjectID, input.Type) - if err != nil { - return projects.Environment{}, err - } - return uc.Repo.Create(ctx, env) -} diff --git a/platform-projects-core/internal/application/project_links/link_billing.go b/platform-projects-core/internal/application/project_links/link_billing.go deleted file mode 100644 index aca1c7c..0000000 --- a/platform-projects-core/internal/application/project_links/link_billing.go +++ /dev/null @@ -1,29 +0,0 @@ -package project_links - -import ( - "context" - - "platform-projects-core/internal/domain/common" - "platform-projects-core/internal/domain/project_links" -) - -type LinkBillingUseCase struct { - Repo LinkRepository -} - -type LinkBillingInput struct { - TenantID string - ProjectID string - External string - Meta map[string]string -} - -func (uc LinkBillingUseCase) Execute(ctx context.Context, input LinkBillingInput) (project_links.BillingLink, error) { - return uc.Repo.LinkBilling(ctx, project_links.BillingLink{ - ID: common.NewID(), - TenantID: input.TenantID, - ProjectID: input.ProjectID, - External: input.External, - Meta: input.Meta, - }) -} diff --git a/platform-projects-core/internal/application/project_links/link_infra.go b/platform-projects-core/internal/application/project_links/link_infra.go deleted file mode 100644 index 139149a..0000000 --- a/platform-projects-core/internal/application/project_links/link_infra.go +++ /dev/null @@ -1,35 +0,0 @@ -package project_links - -import ( - "context" - - "platform-projects-core/internal/domain/common" - "platform-projects-core/internal/domain/project_links" -) - -type LinkRepository interface { - LinkInfra(ctx context.Context, link project_links.InfraLink) (project_links.InfraLink, error) - LinkBilling(ctx context.Context, link project_links.BillingLink) (project_links.BillingLink, error) - LinkSecurity(ctx context.Context, link project_links.SecurityLink) (project_links.SecurityLink, error) -} - -type LinkInfraUseCase struct { - Repo LinkRepository -} - -type LinkInfraInput struct { - TenantID string - ProjectID string - External string - Meta map[string]string -} - -func (uc LinkInfraUseCase) Execute(ctx context.Context, input LinkInfraInput) (project_links.InfraLink, error) { - return uc.Repo.LinkInfra(ctx, project_links.InfraLink{ - ID: common.NewID(), - TenantID: input.TenantID, - ProjectID: input.ProjectID, - External: input.External, - Meta: input.Meta, - }) -} diff --git a/platform-projects-core/internal/application/project_links/link_security.go b/platform-projects-core/internal/application/project_links/link_security.go deleted file mode 100644 index 57235b0..0000000 --- a/platform-projects-core/internal/application/project_links/link_security.go +++ /dev/null @@ -1,29 +0,0 @@ -package project_links - -import ( - "context" - - "platform-projects-core/internal/domain/common" - "platform-projects-core/internal/domain/project_links" -) - -type LinkSecurityUseCase struct { - Repo LinkRepository -} - -type LinkSecurityInput struct { - TenantID string - ProjectID string - External string - Meta map[string]string -} - -func (uc LinkSecurityUseCase) Execute(ctx context.Context, input LinkSecurityInput) (project_links.SecurityLink, error) { - return uc.Repo.LinkSecurity(ctx, project_links.SecurityLink{ - ID: common.NewID(), - TenantID: input.TenantID, - ProjectID: input.ProjectID, - External: input.External, - Meta: input.Meta, - }) -} diff --git a/platform-projects-core/internal/application/projects/archive_project.go b/platform-projects-core/internal/application/projects/archive_project.go deleted file mode 100644 index 65d4653..0000000 --- a/platform-projects-core/internal/application/projects/archive_project.go +++ /dev/null @@ -1,23 +0,0 @@ -package projects - -import ( - "context" - - "platform-projects-core/internal/domain/common" -) - -type ArchiveProjectUseCase struct { - Repo ProjectRepository -} - -type ArchiveProjectInput struct { - TenantID string - ProjectID string -} - -func (uc ArchiveProjectUseCase) Execute(ctx context.Context, input ArchiveProjectInput) error { - if input.TenantID == "" || input.ProjectID == "" { - return common.ErrInvalidInput - } - return uc.Repo.Archive(ctx, input.TenantID, input.ProjectID) -} diff --git a/platform-projects-core/internal/application/projects/create_project.go b/platform-projects-core/internal/application/projects/create_project.go deleted file mode 100644 index d218327..0000000 --- a/platform-projects-core/internal/application/projects/create_project.go +++ /dev/null @@ -1,33 +0,0 @@ -package projects - -import ( - "context" - - "platform-projects-core/internal/domain/projects" -) - -type ProjectRepository interface { - Create(ctx context.Context, project projects.Project) (projects.Project, error) - Update(ctx context.Context, project projects.Project) (projects.Project, error) - Archive(ctx context.Context, tenantID, projectID string) error - List(ctx context.Context, tenantID string) ([]projects.Project, error) -} - -type CreateProjectUseCase struct { - Repo ProjectRepository -} - -type CreateProjectInput struct { - TenantID string - Name string - Slug string - Description string -} - -func (uc CreateProjectUseCase) Execute(ctx context.Context, input CreateProjectInput) (projects.Project, error) { - project, err := projects.NewProject(input.TenantID, input.Name, input.Slug, input.Description) - if err != nil { - return projects.Project{}, err - } - return uc.Repo.Create(ctx, project) -} diff --git a/platform-projects-core/internal/application/projects/list_projects.go b/platform-projects-core/internal/application/projects/list_projects.go deleted file mode 100644 index d37c9f6..0000000 --- a/platform-projects-core/internal/application/projects/list_projects.go +++ /dev/null @@ -1,23 +0,0 @@ -package projects - -import ( - "context" - - "platform-projects-core/internal/domain/common" - "platform-projects-core/internal/domain/projects" -) - -type ListProjectsUseCase struct { - Repo ProjectRepository -} - -type ListProjectsInput struct { - TenantID string -} - -func (uc ListProjectsUseCase) Execute(ctx context.Context, input ListProjectsInput) ([]projects.Project, error) { - if input.TenantID == "" { - return nil, common.ErrInvalidInput - } - return uc.Repo.List(ctx, input.TenantID) -} diff --git a/platform-projects-core/internal/application/projects/update_project.go b/platform-projects-core/internal/application/projects/update_project.go deleted file mode 100644 index 5aa2a37..0000000 --- a/platform-projects-core/internal/application/projects/update_project.go +++ /dev/null @@ -1,40 +0,0 @@ -package projects - -import ( - "context" - "time" - - "platform-projects-core/internal/domain/common" - "platform-projects-core/internal/domain/projects" -) - -type UpdateProjectUseCase struct { - Repo ProjectRepository -} - -type UpdateProjectInput struct { - TenantID string - ProjectID string - Name string - Description string - Status projects.Status -} - -func (uc UpdateProjectUseCase) Execute(ctx context.Context, input UpdateProjectInput) (projects.Project, error) { - if input.TenantID == "" || input.ProjectID == "" { - return projects.Project{}, common.ErrInvalidInput - } - if !input.Status.Valid() { - return projects.Project{}, common.ErrInvalidInput - } - - project := projects.Project{ - ID: input.ProjectID, - TenantID: input.TenantID, - Name: input.Name, - Description: input.Description, - Status: input.Status, - UpdatedAt: time.Now().UTC(), - } - return uc.Repo.Update(ctx, project) -} diff --git a/platform-projects-core/internal/application/repositories/link_repository.go b/platform-projects-core/internal/application/repositories/link_repository.go deleted file mode 100644 index 2005533..0000000 --- a/platform-projects-core/internal/application/repositories/link_repository.go +++ /dev/null @@ -1,31 +0,0 @@ -package repositories - -import ( - "context" - - "platform-projects-core/internal/domain/projects" -) - -type RepositoryRepository interface { - Link(ctx context.Context, repo projects.Repository) (projects.Repository, error) -} - -type LinkRepositoryUseCase struct { - Repo RepositoryRepository -} - -type LinkRepositoryInput struct { - TenantID string - ProjectID string - Provider string - URL string - DefaultBranch string -} - -func (uc LinkRepositoryUseCase) Execute(ctx context.Context, input LinkRepositoryInput) (projects.Repository, error) { - repo, err := projects.NewRepository(input.TenantID, input.ProjectID, input.Provider, input.URL, input.DefaultBranch) - if err != nil { - return projects.Repository{}, err - } - return uc.Repo.Link(ctx, repo) -} diff --git a/platform-projects-core/internal/application/teams/manage_team.go b/platform-projects-core/internal/application/teams/manage_team.go deleted file mode 100644 index 7a00bfa..0000000 --- a/platform-projects-core/internal/application/teams/manage_team.go +++ /dev/null @@ -1,29 +0,0 @@ -package teams - -import ( - "context" - - "platform-projects-core/internal/domain/projects" -) - -type TeamRepository interface { - Upsert(ctx context.Context, team projects.Team) (projects.Team, error) -} - -type ManageTeamUseCase struct { - Repo TeamRepository -} - -type ManageTeamInput struct { - TenantID string - ProjectID string - Name string -} - -func (uc ManageTeamUseCase) Execute(ctx context.Context, input ManageTeamInput) (projects.Team, error) { - team, err := projects.NewTeam(input.TenantID, input.ProjectID, input.Name) - if err != nil { - return projects.Team{}, err - } - return uc.Repo.Upsert(ctx, team) -} diff --git a/platform-projects-core/internal/config/config.go b/platform-projects-core/internal/config/config.go deleted file mode 100644 index b05c0eb..0000000 --- a/platform-projects-core/internal/config/config.go +++ /dev/null @@ -1,101 +0,0 @@ -package config - -import ( - "errors" - "fmt" - "os" - "strconv" - "strings" -) - -type Config struct { - ServiceName string - Environment string - HTTP HTTPConfig - Database DatabaseConfig - Auth AuthConfig -} - -type HTTPConfig struct { - Address string -} - -type DatabaseConfig struct { - DSN string -} - -type AuthConfig struct { - JWKSURL string - Audience string - Issuer string - FallbackSecret string - ClockSkew int -} - -func Load() (Config, error) { - cfg := Config{ - ServiceName: getenvDefault("SERVICE_NAME", "platform-projects-core"), - Environment: getenvDefault("ENVIRONMENT", "development"), - HTTP: HTTPConfig{ - Address: getenvDefault("HTTP_ADDRESS", ":8080"), - }, - Database: DatabaseConfig{ - DSN: os.Getenv("DATABASE_DSN"), - }, - Auth: AuthConfig{ - JWKSURL: os.Getenv("AUTH_JWKS_URL"), - Audience: os.Getenv("AUTH_AUDIENCE"), - Issuer: os.Getenv("AUTH_ISSUER"), - FallbackSecret: os.Getenv("AUTH_FALLBACK_SECRET"), - ClockSkew: parseIntDefault("AUTH_CLOCK_SKEW_SECONDS", 60), - }, - } - - if cfg.Database.DSN == "" { - return Config{}, errors.New("DATABASE_DSN is required") - } - if cfg.Auth.JWKSURL == "" && cfg.Auth.FallbackSecret == "" { - return Config{}, errors.New("AUTH_JWKS_URL or AUTH_FALLBACK_SECRET must be provided") - } - if cfg.Auth.Audience == "" { - return Config{}, errors.New("AUTH_AUDIENCE is required") - } - if cfg.Auth.Issuer == "" { - return Config{}, errors.New("AUTH_ISSUER is required") - } - if err := cfg.Validate(); err != nil { - return Config{}, err - } - - return cfg, nil -} - -func getenvDefault(key, fallback string) string { - value := strings.TrimSpace(os.Getenv(key)) - if value == "" { - return fallback - } - return value -} - -func parseIntDefault(key string, fallback int) int { - value := strings.TrimSpace(os.Getenv(key)) - if value == "" { - return fallback - } - parsed, err := strconv.Atoi(value) - if err != nil { - return fallback - } - return parsed -} - -func (c Config) Validate() error { - if c.ServiceName == "" { - return fmt.Errorf("service name must not be empty") - } - if c.HTTP.Address == "" { - return fmt.Errorf("http address must not be empty") - } - return nil -} diff --git a/platform-projects-core/internal/db/migrations/0001_init.down.sql b/platform-projects-core/internal/db/migrations/0001_init.down.sql deleted file mode 100644 index 8f53f16..0000000 --- a/platform-projects-core/internal/db/migrations/0001_init.down.sql +++ /dev/null @@ -1,8 +0,0 @@ -DROP TABLE IF EXISTS project_security_links; -DROP TABLE IF EXISTS project_billing_links; -DROP TABLE IF EXISTS project_infra_links; -DROP TABLE IF EXISTS teams; -DROP TABLE IF EXISTS repositories; -DROP TABLE IF EXISTS environments; -DROP TABLE IF EXISTS projects; - diff --git a/platform-projects-core/internal/db/migrations/0001_init.up.sql b/platform-projects-core/internal/db/migrations/0001_init.up.sql deleted file mode 100644 index 81ad586..0000000 --- a/platform-projects-core/internal/db/migrations/0001_init.up.sql +++ /dev/null @@ -1,71 +0,0 @@ -CREATE TABLE IF NOT EXISTS projects ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - name TEXT NOT NULL, - slug TEXT NOT NULL, - description TEXT NOT NULL DEFAULT '', - status TEXT NOT NULL, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL, - UNIQUE (tenant_id, slug) -); - -CREATE TABLE IF NOT EXISTS environments ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - project_id UUID NOT NULL REFERENCES projects(id), - type TEXT NOT NULL, - paused BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL -); - -CREATE TABLE IF NOT EXISTS repositories ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - project_id UUID NOT NULL REFERENCES projects(id), - provider TEXT NOT NULL, - url TEXT NOT NULL, - default_branch TEXT NOT NULL -); - -CREATE TABLE IF NOT EXISTS teams ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - project_id UUID NOT NULL REFERENCES projects(id), - name TEXT NOT NULL, - UNIQUE (tenant_id, project_id, name) -); - -CREATE TABLE IF NOT EXISTS project_infra_links ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - project_id UUID NOT NULL REFERENCES projects(id), - external_id TEXT NOT NULL, - meta JSONB NOT NULL DEFAULT '{}'::jsonb -); - -CREATE TABLE IF NOT EXISTS project_billing_links ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - project_id UUID NOT NULL REFERENCES projects(id), - external_id TEXT NOT NULL, - meta JSONB NOT NULL DEFAULT '{}'::jsonb -); - -CREATE TABLE IF NOT EXISTS project_security_links ( - id UUID PRIMARY KEY, - tenant_id UUID NOT NULL, - project_id UUID NOT NULL REFERENCES projects(id), - external_id TEXT NOT NULL, - meta JSONB NOT NULL DEFAULT '{}'::jsonb -); - -CREATE INDEX IF NOT EXISTS projects_tenant_idx ON projects (tenant_id); -CREATE INDEX IF NOT EXISTS environments_tenant_idx ON environments (tenant_id); -CREATE INDEX IF NOT EXISTS repositories_tenant_idx ON repositories (tenant_id); -CREATE INDEX IF NOT EXISTS teams_tenant_idx ON teams (tenant_id); -CREATE INDEX IF NOT EXISTS project_infra_links_tenant_idx ON project_infra_links (tenant_id); -CREATE INDEX IF NOT EXISTS project_billing_links_tenant_idx ON project_billing_links (tenant_id); -CREATE INDEX IF NOT EXISTS project_security_links_tenant_idx ON project_security_links (tenant_id); - diff --git a/platform-projects-core/internal/db/queries/environments.sql b/platform-projects-core/internal/db/queries/environments.sql deleted file mode 100644 index e42155f..0000000 --- a/platform-projects-core/internal/db/queries/environments.sql +++ /dev/null @@ -1,3 +0,0 @@ --- name: CreateEnvironment :exec -INSERT INTO environments (id, tenant_id, project_id, type, paused, created_at, updated_at) -VALUES ($1, $2, $3, $4, $5, $6, $7); diff --git a/platform-projects-core/internal/db/queries/project_links.sql b/platform-projects-core/internal/db/queries/project_links.sql deleted file mode 100644 index 8338641..0000000 --- a/platform-projects-core/internal/db/queries/project_links.sql +++ /dev/null @@ -1,11 +0,0 @@ --- name: LinkInfra :exec -INSERT INTO project_infra_links (id, tenant_id, project_id, external_id, meta) -VALUES ($1, $2, $3, $4, $5); - --- name: LinkBilling :exec -INSERT INTO project_billing_links (id, tenant_id, project_id, external_id, meta) -VALUES ($1, $2, $3, $4, $5); - --- name: LinkSecurity :exec -INSERT INTO project_security_links (id, tenant_id, project_id, external_id, meta) -VALUES ($1, $2, $3, $4, $5); diff --git a/platform-projects-core/internal/db/queries/projects.sql b/platform-projects-core/internal/db/queries/projects.sql deleted file mode 100644 index f04ba27..0000000 --- a/platform-projects-core/internal/db/queries/projects.sql +++ /dev/null @@ -1,22 +0,0 @@ --- name: CreateProject :exec -INSERT INTO projects (id, tenant_id, name, slug, description, status, created_at, updated_at) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8); - --- name: UpdateProject :exec -UPDATE projects -SET name = $1, - description = $2, - status = $3, - updated_at = $4 -WHERE tenant_id = $5 AND id = $6; - --- name: ArchiveProject :exec -UPDATE projects -SET status = 'archived', updated_at = $1 -WHERE tenant_id = $2 AND id = $3; - --- name: ListProjects :many -SELECT id, tenant_id, name, slug, description, status, created_at, updated_at -FROM projects -WHERE tenant_id = $1 -ORDER BY created_at DESC; diff --git a/platform-projects-core/internal/db/queries/repositories.sql b/platform-projects-core/internal/db/queries/repositories.sql deleted file mode 100644 index acf04cc..0000000 --- a/platform-projects-core/internal/db/queries/repositories.sql +++ /dev/null @@ -1,3 +0,0 @@ --- name: LinkRepository :exec -INSERT INTO repositories (id, tenant_id, project_id, provider, url, default_branch) -VALUES ($1, $2, $3, $4, $5, $6); diff --git a/platform-projects-core/internal/db/queries/teams.sql b/platform-projects-core/internal/db/queries/teams.sql deleted file mode 100644 index c5edf10..0000000 --- a/platform-projects-core/internal/db/queries/teams.sql +++ /dev/null @@ -1,4 +0,0 @@ --- name: UpsertTeam :exec -INSERT INTO teams (id, tenant_id, project_id, name) -VALUES ($1, $2, $3, $4) -ON CONFLICT (tenant_id, project_id, name) DO UPDATE SET name = excluded.name; diff --git a/platform-projects-core/internal/db/sqlc/sqlc.yaml b/platform-projects-core/internal/db/sqlc/sqlc.yaml deleted file mode 100644 index 6c2af45..0000000 --- a/platform-projects-core/internal/db/sqlc/sqlc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -version: "2" -sql: - - schema: "../migrations/0001_init.up.sql" - queries: "../queries" - engine: "postgresql" - gen: - go: - package: "generated" - out: "../generated" - sql_package: "pgx/v5" diff --git a/platform-projects-core/internal/domain/common/errors.go b/platform-projects-core/internal/domain/common/errors.go deleted file mode 100644 index db3e056..0000000 --- a/platform-projects-core/internal/domain/common/errors.go +++ /dev/null @@ -1,12 +0,0 @@ -package common - -import "errors" - -var ( - ErrUnauthorized = errors.New("unauthorized") - ErrForbidden = errors.New("forbidden") - ErrNotFound = errors.New("not found") - ErrConflict = errors.New("conflict") - ErrInvalidInput = errors.New("invalid input") - ErrPreconditionFail = errors.New("precondition failed") -) diff --git a/platform-projects-core/internal/domain/common/identifiers.go b/platform-projects-core/internal/domain/common/identifiers.go deleted file mode 100644 index 7bd076a..0000000 --- a/platform-projects-core/internal/domain/common/identifiers.go +++ /dev/null @@ -1,7 +0,0 @@ -package common - -import "github.com/google/uuid" - -func NewID() string { - return uuid.NewString() -} diff --git a/platform-projects-core/internal/domain/project_links/billing_link.go b/platform-projects-core/internal/domain/project_links/billing_link.go deleted file mode 100644 index 6e6eaf1..0000000 --- a/platform-projects-core/internal/domain/project_links/billing_link.go +++ /dev/null @@ -1,9 +0,0 @@ -package project_links - -type BillingLink struct { - ID string - TenantID string - ProjectID string - External string - Meta map[string]string -} diff --git a/platform-projects-core/internal/domain/project_links/infra_link.go b/platform-projects-core/internal/domain/project_links/infra_link.go deleted file mode 100644 index 7cd3960..0000000 --- a/platform-projects-core/internal/domain/project_links/infra_link.go +++ /dev/null @@ -1,9 +0,0 @@ -package project_links - -type InfraLink struct { - ID string - TenantID string - ProjectID string - External string - Meta map[string]string -} diff --git a/platform-projects-core/internal/domain/project_links/security_link.go b/platform-projects-core/internal/domain/project_links/security_link.go deleted file mode 100644 index 66104b5..0000000 --- a/platform-projects-core/internal/domain/project_links/security_link.go +++ /dev/null @@ -1,9 +0,0 @@ -package project_links - -type SecurityLink struct { - ID string - TenantID string - ProjectID string - External string - Meta map[string]string -} diff --git a/platform-projects-core/internal/domain/projects/environment.go b/platform-projects-core/internal/domain/projects/environment.go deleted file mode 100644 index 08ec5ae..0000000 --- a/platform-projects-core/internal/domain/projects/environment.go +++ /dev/null @@ -1,32 +0,0 @@ -package projects - -import ( - "strings" - "time" - - "platform-projects-core/internal/domain/common" -) - -type Environment struct { - ID string - TenantID string - ProjectID string - Type string - Paused bool - CreatedAt time.Time - UpdatedAt time.Time -} - -func NewEnvironment(tenantID, projectID, envType string) (Environment, error) { - if tenantID == "" || projectID == "" || strings.TrimSpace(envType) == "" { - return Environment{}, common.ErrInvalidInput - } - return Environment{ - ID: common.NewID(), - TenantID: tenantID, - ProjectID: projectID, - Type: strings.TrimSpace(envType), - CreatedAt: time.Now().UTC(), - UpdatedAt: time.Now().UTC(), - }, nil -} diff --git a/platform-projects-core/internal/domain/projects/project.go b/platform-projects-core/internal/domain/projects/project.go deleted file mode 100644 index 5d0448a..0000000 --- a/platform-projects-core/internal/domain/projects/project.go +++ /dev/null @@ -1,41 +0,0 @@ -package projects - -import ( - "strings" - "time" - - "platform-projects-core/internal/domain/common" -) - -type Project struct { - ID string - TenantID string - Name string - Slug string - Description string - Status Status - CreatedAt time.Time - UpdatedAt time.Time -} - -func NewProject(tenantID, name, slug, description string) (Project, error) { - slug = strings.TrimSpace(slug) - name = strings.TrimSpace(name) - if tenantID == "" || name == "" || slug == "" { - return Project{}, common.ErrInvalidInput - } - return Project{ - ID: common.NewID(), - TenantID: tenantID, - Name: name, - Slug: slug, - Description: strings.TrimSpace(description), - Status: StatusActive, - CreatedAt: time.Now().UTC(), - UpdatedAt: time.Now().UTC(), - }, nil -} - -func (p Project) CanLink() bool { - return p.Status != StatusArchived -} diff --git a/platform-projects-core/internal/domain/projects/repository.go b/platform-projects-core/internal/domain/projects/repository.go deleted file mode 100644 index 88c4678..0000000 --- a/platform-projects-core/internal/domain/projects/repository.go +++ /dev/null @@ -1,38 +0,0 @@ -package projects - -import ( - "net/url" - "strings" - - "platform-projects-core/internal/domain/common" -) - -type Repository struct { - ID string - TenantID string - ProjectID string - Provider string - URL string - DefaultBranch string -} - -func NewRepository(tenantID, projectID, provider, repoURL, defaultBranch string) (Repository, error) { - if tenantID == "" || projectID == "" || strings.TrimSpace(provider) == "" { - return Repository{}, common.ErrInvalidInput - } - parsed, err := url.ParseRequestURI(repoURL) - if err != nil || parsed.Scheme == "" || parsed.Host == "" { - return Repository{}, common.ErrInvalidInput - } - if strings.TrimSpace(defaultBranch) == "" { - return Repository{}, common.ErrInvalidInput - } - return Repository{ - ID: common.NewID(), - TenantID: tenantID, - ProjectID: projectID, - Provider: strings.TrimSpace(provider), - URL: repoURL, - DefaultBranch: strings.TrimSpace(defaultBranch), - }, nil -} diff --git a/platform-projects-core/internal/domain/projects/status.go b/platform-projects-core/internal/domain/projects/status.go deleted file mode 100644 index f00d8a3..0000000 --- a/platform-projects-core/internal/domain/projects/status.go +++ /dev/null @@ -1,18 +0,0 @@ -package projects - -type Status string - -const ( - StatusActive Status = "active" - StatusPaused Status = "paused" - StatusArchived Status = "archived" -) - -func (s Status) Valid() bool { - switch s { - case StatusActive, StatusPaused, StatusArchived: - return true - default: - return false - } -} diff --git a/platform-projects-core/internal/domain/projects/team.go b/platform-projects-core/internal/domain/projects/team.go deleted file mode 100644 index bbf6f23..0000000 --- a/platform-projects-core/internal/domain/projects/team.go +++ /dev/null @@ -1,26 +0,0 @@ -package projects - -import ( - "strings" - - "platform-projects-core/internal/domain/common" -) - -type Team struct { - ID string - TenantID string - ProjectID string - Name string -} - -func NewTeam(tenantID, projectID, name string) (Team, error) { - if tenantID == "" || projectID == "" || strings.TrimSpace(name) == "" { - return Team{}, common.ErrInvalidInput - } - return Team{ - ID: common.NewID(), - TenantID: tenantID, - ProjectID: projectID, - Name: strings.TrimSpace(name), - }, nil -} diff --git a/platform-projects-core/internal/infrastructure/auth/claims.go b/platform-projects-core/internal/infrastructure/auth/claims.go deleted file mode 100644 index d908166..0000000 --- a/platform-projects-core/internal/infrastructure/auth/claims.go +++ /dev/null @@ -1,10 +0,0 @@ -package auth - -type Claims struct { - Subject string `json:"sub"` - TenantID string `json:"tenantId"` - Roles []string `json:"roles"` - Issuer string `json:"iss"` - Audience []string `json:"aud"` - Expires int64 `json:"exp"` -} diff --git a/platform-projects-core/internal/infrastructure/auth/jwt_middleware.go b/platform-projects-core/internal/infrastructure/auth/jwt_middleware.go deleted file mode 100644 index 41e1fdf..0000000 --- a/platform-projects-core/internal/infrastructure/auth/jwt_middleware.go +++ /dev/null @@ -1,128 +0,0 @@ -package auth - -import ( - "context" - "errors" - "fmt" - "net/http" - "strings" - "time" - - "github.com/MicahParks/keyfunc/v2" - "github.com/golang-jwt/jwt/v5" - "platform-projects-core/internal/api/transport" - "platform-projects-core/internal/config" - "platform-projects-core/internal/domain/common" -) - -type Validator struct { - jwks *keyfunc.JWKS - audience string - issuer string - fallbackKey []byte - clockSkew time.Duration -} - -func NewValidator(ctx context.Context, cfg config.AuthConfig) (*Validator, error) { - validator := &Validator{ - audience: cfg.Audience, - issuer: cfg.Issuer, - clockSkew: time.Duration(cfg.ClockSkew) * time.Second, - } - if cfg.FallbackSecret != "" { - validator.fallbackKey = []byte(cfg.FallbackSecret) - } - if cfg.JWKSURL == "" { - return validator, nil - } - jwks, err := keyfunc.Get(cfg.JWKSURL, keyfunc.Options{RefreshTimeout: 5 * time.Second}) - if err != nil { - return nil, fmt.Errorf("jwks fetch: %w", err) - } - validator.jwks = jwks - return validator, nil -} - -func Middleware(validator *Validator) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - claims, err := validator.Validate(r.Context(), r.Header.Get("Authorization")) - if err != nil { - transport.WriteError(w, http.StatusUnauthorized, "unauthorized", "invalid token") - return - } - ctx := transport.WithTenant(r.Context(), claims.TenantID) - next.ServeHTTP(w, r.WithContext(ctx)) - }) - } -} - -func (v *Validator) Validate(ctx context.Context, authHeader string) (Claims, error) { - parts := strings.Fields(authHeader) - if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { - return Claims{}, common.ErrUnauthorized - } - jwtToken := parts[1] - - parser := jwt.NewParser(jwt.WithAudience(v.audience), jwt.WithIssuer(v.issuer), jwt.WithLeeway(v.clockSkew)) - parsed, err := parser.Parse(jwtToken, v.keyFunc) - if err != nil || !parsed.Valid { - return Claims{}, common.ErrUnauthorized - } - mapClaims, ok := parsed.Claims.(jwt.MapClaims) - if !ok { - return Claims{}, common.ErrUnauthorized - } - - claims := Claims{ - Subject: getString(mapClaims, "sub"), - TenantID: getString(mapClaims, "tenantId"), - Roles: getStringSlice(mapClaims, "roles"), - Issuer: getString(mapClaims, "iss"), - Audience: getStringSlice(mapClaims, "aud"), - } - if claims.Subject == "" || claims.TenantID == "" || len(claims.Roles) == 0 { - return Claims{}, common.ErrUnauthorized - } - return claims, nil -} - -func (v *Validator) keyFunc(token *jwt.Token) (any, error) { - if v.jwks != nil { - return v.jwks.Keyfunc(token) - } - if len(v.fallbackKey) > 0 { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, errors.New("unexpected signing method") - } - return v.fallbackKey, nil - } - return nil, errors.New("no jwks or fallback secret configured") -} - -func getString(claims jwt.MapClaims, key string) string { - if value, ok := claims[key].(string); ok { - return value - } - return "" -} - -func getStringSlice(claims jwt.MapClaims, key string) []string { - value, ok := claims[key] - if !ok { - return nil - } - if slice, ok := value.([]any); ok { - out := make([]string, 0, len(slice)) - for _, item := range slice { - if str, ok := item.(string); ok { - out = append(out, str) - } - } - return out - } - if str, ok := value.(string); ok { - return []string{str} - } - return nil -} diff --git a/platform-projects-core/internal/infrastructure/postgres/pool.go b/platform-projects-core/internal/infrastructure/postgres/pool.go deleted file mode 100644 index ab44b24..0000000 --- a/platform-projects-core/internal/infrastructure/postgres/pool.go +++ /dev/null @@ -1,28 +0,0 @@ -package postgres - -import ( - "context" - "fmt" - "time" - - "github.com/jackc/pgx/v5/pgxpool" - "platform-projects-core/internal/config" -) - -func NewPool(ctx context.Context, cfg config.DatabaseConfig) (*pgxpool.Pool, error) { - poolCfg, err := pgxpool.ParseConfig(cfg.DSN) - if err != nil { - return nil, fmt.Errorf("parse dsn: %w", err) - } - poolCfg.MaxConnLifetime = time.Hour - poolCfg.MaxConnIdleTime = 30 * time.Minute - pool, err := pgxpool.NewWithConfig(ctx, poolCfg) - if err != nil { - return nil, fmt.Errorf("connect: %w", err) - } - if err := pool.Ping(ctx); err != nil { - pool.Close() - return nil, fmt.Errorf("ping: %w", err) - } - return pool, nil -} diff --git a/platform-projects-core/internal/infrastructure/repositories/environments_repo_sqlc.go b/platform-projects-core/internal/infrastructure/repositories/environments_repo_sqlc.go deleted file mode 100644 index 27ee660..0000000 --- a/platform-projects-core/internal/infrastructure/repositories/environments_repo_sqlc.go +++ /dev/null @@ -1,26 +0,0 @@ -package repositories - -import ( - "context" - - "github.com/jackc/pgx/v5/pgxpool" - "platform-projects-core/internal/domain/projects" -) - -type EnvironmentsRepository struct { - pool *pgxpool.Pool -} - -func NewEnvironmentsRepository(pool *pgxpool.Pool) *EnvironmentsRepository { - return &EnvironmentsRepository{pool: pool} -} - -func (r *EnvironmentsRepository) Create(ctx context.Context, env projects.Environment) (projects.Environment, error) { - query := `INSERT INTO environments (id, tenant_id, project_id, type, paused, created_at, updated_at) - VALUES ($1,$2,$3,$4,$5,$6,$7)` - _, err := r.pool.Exec(ctx, query, env.ID, env.TenantID, env.ProjectID, env.Type, env.Paused, env.CreatedAt, env.UpdatedAt) - if err != nil { - return projects.Environment{}, err - } - return env, nil -} diff --git a/platform-projects-core/internal/infrastructure/repositories/project_links_repo_sqlc.go b/platform-projects-core/internal/infrastructure/repositories/project_links_repo_sqlc.go deleted file mode 100644 index 9a7110e..0000000 --- a/platform-projects-core/internal/infrastructure/repositories/project_links_repo_sqlc.go +++ /dev/null @@ -1,50 +0,0 @@ -package repositories - -import ( - "context" - "encoding/json" - - "github.com/jackc/pgx/v5/pgxpool" - "platform-projects-core/internal/domain/project_links" -) - -type ProjectLinksRepository struct { - pool *pgxpool.Pool -} - -func NewProjectLinksRepository(pool *pgxpool.Pool) *ProjectLinksRepository { - return &ProjectLinksRepository{pool: pool} -} - -func (r *ProjectLinksRepository) LinkInfra(ctx context.Context, link project_links.InfraLink) (project_links.InfraLink, error) { - meta, _ := json.Marshal(link.Meta) - query := `INSERT INTO project_infra_links (id, tenant_id, project_id, external_id, meta) - VALUES ($1,$2,$3,$4,$5)` - _, err := r.pool.Exec(ctx, query, link.ID, link.TenantID, link.ProjectID, link.External, meta) - if err != nil { - return project_links.InfraLink{}, err - } - return link, nil -} - -func (r *ProjectLinksRepository) LinkBilling(ctx context.Context, link project_links.BillingLink) (project_links.BillingLink, error) { - meta, _ := json.Marshal(link.Meta) - query := `INSERT INTO project_billing_links (id, tenant_id, project_id, external_id, meta) - VALUES ($1,$2,$3,$4,$5)` - _, err := r.pool.Exec(ctx, query, link.ID, link.TenantID, link.ProjectID, link.External, meta) - if err != nil { - return project_links.BillingLink{}, err - } - return link, nil -} - -func (r *ProjectLinksRepository) LinkSecurity(ctx context.Context, link project_links.SecurityLink) (project_links.SecurityLink, error) { - meta, _ := json.Marshal(link.Meta) - query := `INSERT INTO project_security_links (id, tenant_id, project_id, external_id, meta) - VALUES ($1,$2,$3,$4,$5)` - _, err := r.pool.Exec(ctx, query, link.ID, link.TenantID, link.ProjectID, link.External, meta) - if err != nil { - return project_links.SecurityLink{}, err - } - return link, nil -} diff --git a/platform-projects-core/internal/infrastructure/repositories/projects_repo_sqlc.go b/platform-projects-core/internal/infrastructure/repositories/projects_repo_sqlc.go deleted file mode 100644 index 24b8c2d..0000000 --- a/platform-projects-core/internal/infrastructure/repositories/projects_repo_sqlc.go +++ /dev/null @@ -1,72 +0,0 @@ -package repositories - -import ( - "context" - "time" - - "github.com/jackc/pgx/v5/pgxpool" - "platform-projects-core/internal/domain/common" - "platform-projects-core/internal/domain/projects" -) - -type ProjectsRepository struct { - pool *pgxpool.Pool -} - -func NewProjectsRepository(pool *pgxpool.Pool) *ProjectsRepository { - return &ProjectsRepository{pool: pool} -} - -func (r *ProjectsRepository) Create(ctx context.Context, project projects.Project) (projects.Project, error) { - query := `INSERT INTO projects (id, tenant_id, name, slug, description, status, created_at, updated_at) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8)` - _, err := r.pool.Exec(ctx, query, project.ID, project.TenantID, project.Name, project.Slug, project.Description, project.Status, project.CreatedAt, project.UpdatedAt) - if err != nil { - return projects.Project{}, err - } - return project, nil -} - -func (r *ProjectsRepository) Update(ctx context.Context, project projects.Project) (projects.Project, error) { - query := `UPDATE projects SET name=$1, description=$2, status=$3, updated_at=$4 WHERE tenant_id=$5 AND id=$6` - cmd, err := r.pool.Exec(ctx, query, project.Name, project.Description, project.Status, time.Now().UTC(), project.TenantID, project.ID) - if err != nil { - return projects.Project{}, err - } - if cmd.RowsAffected() == 0 { - return projects.Project{}, common.ErrNotFound - } - return project, nil -} - -func (r *ProjectsRepository) Archive(ctx context.Context, tenantID, projectID string) error { - query := `UPDATE projects SET status='archived', updated_at=$1 WHERE tenant_id=$2 AND id=$3` - cmd, err := r.pool.Exec(ctx, query, time.Now().UTC(), tenantID, projectID) - if err != nil { - return err - } - if cmd.RowsAffected() == 0 { - return common.ErrNotFound - } - return nil -} - -func (r *ProjectsRepository) List(ctx context.Context, tenantID string) ([]projects.Project, error) { - query := `SELECT id, tenant_id, name, slug, description, status, created_at, updated_at FROM projects WHERE tenant_id=$1 ORDER BY created_at DESC` - rows, err := r.pool.Query(ctx, query, tenantID) - if err != nil { - return nil, err - } - defer rows.Close() - - var results []projects.Project - for rows.Next() { - var p projects.Project - err := rows.Scan(&p.ID, &p.TenantID, &p.Name, &p.Slug, &p.Description, &p.Status, &p.CreatedAt, &p.UpdatedAt) - if err != nil { - return nil, err - } - results = append(results, p) - } - return results, rows.Err() -} diff --git a/platform-projects-core/internal/infrastructure/repositories/repositories_repo_sqlc.go b/platform-projects-core/internal/infrastructure/repositories/repositories_repo_sqlc.go deleted file mode 100644 index 39c9b2e..0000000 --- a/platform-projects-core/internal/infrastructure/repositories/repositories_repo_sqlc.go +++ /dev/null @@ -1,26 +0,0 @@ -package repositories - -import ( - "context" - - "github.com/jackc/pgx/v5/pgxpool" - "platform-projects-core/internal/domain/projects" -) - -type RepositoriesRepository struct { - pool *pgxpool.Pool -} - -func NewRepositoriesRepository(pool *pgxpool.Pool) *RepositoriesRepository { - return &RepositoriesRepository{pool: pool} -} - -func (r *RepositoriesRepository) Link(ctx context.Context, repo projects.Repository) (projects.Repository, error) { - query := `INSERT INTO repositories (id, tenant_id, project_id, provider, url, default_branch) - VALUES ($1,$2,$3,$4,$5,$6)` - _, err := r.pool.Exec(ctx, query, repo.ID, repo.TenantID, repo.ProjectID, repo.Provider, repo.URL, repo.DefaultBranch) - if err != nil { - return projects.Repository{}, err - } - return repo, nil -} diff --git a/platform-projects-core/internal/infrastructure/repositories/teams_repo_sqlc.go b/platform-projects-core/internal/infrastructure/repositories/teams_repo_sqlc.go deleted file mode 100644 index 28d9724..0000000 --- a/platform-projects-core/internal/infrastructure/repositories/teams_repo_sqlc.go +++ /dev/null @@ -1,27 +0,0 @@ -package repositories - -import ( - "context" - - "github.com/jackc/pgx/v5/pgxpool" - "platform-projects-core/internal/domain/projects" -) - -type TeamsRepository struct { - pool *pgxpool.Pool -} - -func NewTeamsRepository(pool *pgxpool.Pool) *TeamsRepository { - return &TeamsRepository{pool: pool} -} - -func (r *TeamsRepository) Upsert(ctx context.Context, team projects.Team) (projects.Team, error) { - query := `INSERT INTO teams (id, tenant_id, project_id, name) - VALUES ($1,$2,$3,$4) - ON CONFLICT (tenant_id, project_id, name) DO UPDATE SET name=excluded.name` - _, err := r.pool.Exec(ctx, query, team.ID, team.TenantID, team.ProjectID, team.Name) - if err != nil { - return projects.Team{}, err - } - return team, nil -} diff --git a/platform-projects-core/internal/observability/logging.go b/platform-projects-core/internal/observability/logging.go deleted file mode 100644 index 2bed391..0000000 --- a/platform-projects-core/internal/observability/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package observability - -import ( - "log/slog" - "os" -) - -func NewLogger(environment string) *slog.Logger { - level := new(slog.LevelVar) - if environment == "production" { - level.Set(slog.LevelInfo) - } else { - level.Set(slog.LevelDebug) - } - - return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level})) -} diff --git a/platform-projects-core/internal/observability/metrics.go b/platform-projects-core/internal/observability/metrics.go deleted file mode 100644 index 2175d74..0000000 --- a/platform-projects-core/internal/observability/metrics.go +++ /dev/null @@ -1,9 +0,0 @@ -package observability - -import "net/http" - -func MetricsHandler() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) - }) -} diff --git a/repo-integrations-core/.dockerignore b/repo-integrations-core/.dockerignore deleted file mode 100644 index 37ecc40..0000000 --- a/repo-integrations-core/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -.git -.env -.gitignore -Dockerfile -README.md -REPO-INTEGRATIONS-CORE.md -migrations -*.log diff --git a/repo-integrations-core/.env.example b/repo-integrations-core/.env.example deleted file mode 100644 index 17ddc68..0000000 --- a/repo-integrations-core/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -DATABASE_URL= -JWT_SECRET= -ENCRYPTION_KEY= -GITHUB_CLIENT_ID= -GITHUB_SECRET= diff --git a/repo-integrations-core/.gitignore b/repo-integrations-core/.gitignore deleted file mode 100644 index 5fd8307..0000000 --- a/repo-integrations-core/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.env -*.log -.DS_Store -repo-integrations-core -main -coverage diff --git a/repo-integrations-core/Dockerfile b/repo-integrations-core/Dockerfile deleted file mode 100644 index ba7f3c1..0000000 --- a/repo-integrations-core/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -# Dockerfile -FROM docker.io/library/golang:1.23-alpine AS builder - -WORKDIR /app - -COPY go.mod go.sum ./ -RUN go mod download - -COPY . . - -# Build with optimization flags -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /app/repo-integrations-core ./cmd/api - -# Use Google Distroless static image for minimal size and security -FROM gcr.io/distroless/static:nonroot - -WORKDIR /app - -COPY --from=builder /app/repo-integrations-core . - -USER nonroot:nonroot - -EXPOSE 8080 - -CMD ["./repo-integrations-core"] diff --git a/repo-integrations-core/Makefile b/repo-integrations-core/Makefile deleted file mode 100644 index e642305..0000000 --- a/repo-integrations-core/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -.PHONY: up down build logs - -up: - docker-compose up -d - -down: - docker-compose down - -build: - docker-compose build - -logs: - docker-compose logs -f - -# SQLC -sqlc-generate: - docker run --rm -v "$(PWD):/src" -w /src sqlc/sqlc:1.25.0 generate - -# Migrations -migrate-create: - @read -p "Enter migration name: " name; \ - migrate create -ext sql -dir migrations -seq $$name - -migrate-up: - migrate -path migrations -database "postgres://user:password@localhost:5432/repo_integrations?sslmode=disable" -verbose up - -migrate-down: - migrate -path migrations -database "postgres://user:password@localhost:5432/repo_integrations?sslmode=disable" -verbose down - -.PHONY: sqlc-generate migrate-create migrate-up migrate-down diff --git a/repo-integrations-core/REPO-INTEGRATIONS-CORE.md b/repo-integrations-core/REPO-INTEGRATIONS-CORE.md deleted file mode 100644 index 61eb095..0000000 --- a/repo-integrations-core/REPO-INTEGRATIONS-CORE.md +++ /dev/null @@ -1,73 +0,0 @@ -# REPO-INTEGRATIONS-CORE - -O `repo-integrations-core` é o serviço responsável por gerenciar o ciclo de vida de integrações com provedores de controle de versão (Git). - -## 📋 Visão Geral - -Este serviço atua como um middleware inteligente entre a plataforma e provedores externos (GitHub, GitLab, Bitbucket), normalizando fluxos de autenticação (OAuth) e ingestão de eventos (Webhooks). - -### Arquitetura - -```mermaid -graph LR - User[Usuário] -->|OAuth Flow| API[Repo Integrations] - API -->|Exchange Code| GitHub[GitHub API] - API -->|Store Token| DB[(Encrypted DB)] - - GitHub -->|Webhook| API - API -->|Normalize| EventBus[Event Bus / Queue] -``` - -## 🚀 Estrutura do Projeto - -O projeto é escrito em **Go** e estrutura-se da seguinte forma: - -| Diretório | Descrição | -| :--- | :--- | -| `cmd/api` | Entrypoint. | -| `internal/oauth` | Fluxos de autenticação. | -| `internal/webhooks` | Processamento e validação de webhooks. | -| `internal/provider` | Abstrações para diferentes providers (GitHub, GitLab). | - -## 🛠️ Tecnologias e Otimizações - -- **Linguagem**: Go 1.23. -- **Database**: PostgreSQL (tokens criptografados). -- **Segurança**: Criptografia AES-GCM para tokens de acesso. -- **Containerização**: - - Base `gcr.io/distroless/static:nonroot`. - - Build estático. - -## 💻 Como Executar - -### Docker (Recomendado) - -```bash -# Build -docker build -t repo-integrations-core . - -# Run -docker run -p 8080:8080 --env-file .env repo-integrations-core -``` -Disponível na porta `8080`. - -### Desenvolvimento - -1. **Setup**: - ```bash - cp .env.example .env - go mod tidy - ``` -2. **App GitHub**: Você precisará criar um GitHub App e configurar `GITHUB_CLIENT_ID` e `GITHUB_SECRET`. -3. **Executar**: - ```bash - go run ./cmd/api - ``` - -## 🔧 Detalhes do Dockerfile - -O `Dockerfile` segue o padrão de otimização da plataforma: - -- **Builder**: `golang:1.23-alpine`. -- **Runtime**: `distroless/static` (sem shell, non-root). -- **Compilação**: Flags `-w -s` para redução de binário. diff --git a/repo-integrations-core/cmd/api/main.go b/repo-integrations-core/cmd/api/main.go deleted file mode 100644 index 237a881..0000000 --- a/repo-integrations-core/cmd/api/main.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "context" - "log" - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "github.com/jackc/pgx/v5/pgxpool" - "github.com/lab/repo-integrations-core/internal/api" - "github.com/lab/repo-integrations-core/internal/config" - "github.com/lab/repo-integrations-core/internal/db" -) - -func main() { - cfg := config.Load() - - pool, err := pgxpool.New(context.Background(), cfg.DatabaseURL) - if err != nil { - log.Fatalf("Unable to connect to database: %v\n", err) - } - defer pool.Close() - - queries := db.New(pool) - apiHandler := api.New(cfg, queries) - - r := chi.NewRouter() - r.Use(middleware.Logger) - r.Use(middleware.Recoverer) - - r.Get("/", func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Repo Integrations Core")) - }) - - r.Route("/integrations/github", func(r chi.Router) { - r.Get("/connect", apiHandler.ConnectGithubHandler) - r.Get("/callback", apiHandler.ConnectGithubCallbackHandler) - }) - - r.Post("/webhooks/github", apiHandler.GithubWebhookHandler) - - r.Get("/repositories", apiHandler.ListRepositoriesHandler) - - log.Println("Starting server on :8080") - if err := http.ListenAndServe(":8080", r); err != nil { - log.Fatalf("Could not start server: %s\n", err) - } -} \ No newline at end of file diff --git a/repo-integrations-core/db/query.sql b/repo-integrations-core/db/query.sql deleted file mode 100644 index a50594d..0000000 --- a/repo-integrations-core/db/query.sql +++ /dev/null @@ -1,85 +0,0 @@ --- name: CreateRepoAccount :one -INSERT INTO repo_accounts ( - tenant_id, - provider, - account_id, - username, - encrypted_access_token, - encrypted_refresh_token -) VALUES ( - $1, $2, $3, $4, $5, $6 -) RETURNING *; - --- name: GetRepoAccount :one -SELECT * FROM repo_accounts -WHERE tenant_id = $1 AND provider = $2 AND account_id = $3; - --- name: GetRepoAccountByID :one -SELECT * FROM repo_accounts -WHERE tenant_id = $1 AND id = $2; - --- name: UpdateRepoAccountTokens :one -UPDATE repo_accounts -SET - encrypted_access_token = $3, - encrypted_refresh_token = $4, - updated_at = NOW() -WHERE tenant_id = $1 AND id = $2 -RETURNING *; - --- name: ListRepoAccountsByTenant :many -SELECT * FROM repo_accounts -WHERE tenant_id = $1; - --- name: CreateRepository :one -INSERT INTO repositories ( - tenant_id, - repo_account_id, - external_id, - name, - url -) VALUES ( - $1, $2, $3, $4, $5 -) RETURNING *; - --- name: GetRepository :one -SELECT * FROM repositories -WHERE tenant_id = $1 AND id = $2; - --- name: GetRepositoryByExternalID :one -SELECT * FROM repositories -WHERE tenant_id = $1 AND external_id = $2; - --- name: ListRepositoriesByTenant :many -SELECT * FROM repositories -WHERE tenant_id = $1 AND is_active = TRUE; - --- name: CreateWebhook :one -INSERT INTO webhooks ( - tenant_id, - repository_id, - external_id, - secret -) VALUES ( - $1, $2, $3, $4 -) RETURNING *; - --- name: GetWebhookByRepoID :one -SELECT * FROM webhooks -WHERE tenant_id = $1 AND repository_id = $2; - --- name: GetWebhookByRepoExternalID :one -SELECT w.* FROM webhooks w -JOIN repositories r ON w.repository_id = r.id -WHERE r.tenant_id = $1 AND r.external_id = $2; - --- name: CreateRepoEvent :one -INSERT INTO repo_events ( - tenant_id, - repository_id, - event_type, - payload -) VALUES ( - $1, $2, $3, $4 -) RETURNING *; - diff --git a/repo-integrations-core/go.mod b/repo-integrations-core/go.mod deleted file mode 100644 index 844c1fc..0000000 --- a/repo-integrations-core/go.mod +++ /dev/null @@ -1,25 +0,0 @@ -module github.com/lab/repo-integrations-core - -go 1.23 - -require ( - github.com/go-chi/chi/v5 v5.2.3 - github.com/google/go-github/v53 v53.2.0 - github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.7.1 - github.com/joho/godotenv v1.5.1 - golang.org/x/oauth2 v0.24.0 -) - -require ( - github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect - github.com/cloudflare/circl v1.3.3 // indirect - github.com/google/go-querystring v1.1.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.21.0 // indirect -) diff --git a/repo-integrations-core/go.sum b/repo-integrations-core/go.sum deleted file mode 100644 index 568ec82..0000000 --- a/repo-integrations-core/go.sum +++ /dev/null @@ -1,60 +0,0 @@ -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI= -github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= -github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/repo-integrations-core/internal/api/github.go b/repo-integrations-core/internal/api/github.go deleted file mode 100644 index db5ae3d..0000000 --- a/repo-integrations-core/internal/api/github.go +++ /dev/null @@ -1,198 +0,0 @@ -package api - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - - "github.com/google/go-github/v53/github" - "github.com/google/uuid" - "github.com/jackc/pgx/v5/pgtype" - "github.com/lab/repo-integrations-core/internal/config" - "github.com/lab/repo-integrations-core/internal/crypto" - "github.com/lab/repo-integrations-core/internal/db" - "golang.org/x/oauth2" - oauth2github "golang.org/x/oauth2/github" -) - -type API struct { - config *config.Config - queries *db.Queries -} - -func New(config *config.Config, queries *db.Queries) *API { - return &API{ - config: config, - queries: queries, - } -} - -func (a *API) getGithubOAuthConfig() *oauth2.Config { - return &oauth2.Config{ - ClientID: a.config.GithubClientID, - ClientSecret: a.config.GithubSecret, - Endpoint: oauth2github.Endpoint, - RedirectURL: "http://localhost:8080/integrations/github/callback", - Scopes: []string{"repo", "admin:repo_hook"}, - } -} - -func (a *API) ConnectGithubHandler(w http.ResponseWriter, r *http.Request) { - // For now, we'll hardcode the tenant_id. In a real app, this would come from the JWT. - tenantID := uuid.New() - url := a.getGithubOAuthConfig().AuthCodeURL(tenantID.String(), oauth2.AccessTypeOffline) - http.Redirect(w, r, url, http.StatusTemporaryRedirect) -} - -type githubUser struct { - ID int64 `json:"id"` - Login string `json:"login"` -} - -func (a *API) ConnectGithubCallbackHandler(w http.ResponseWriter, r *http.Request) { - state := r.URL.Query().Get("state") - code := r.URL.Query().Get("code") - - tenantID, err := uuid.Parse(state) - if err != nil { - http.Error(w, "Invalid state", http.StatusBadRequest) - return - } - - githubOauthConfig := a.getGithubOAuthConfig() - token, err := githubOauthConfig.Exchange(context.Background(), code) - if err != nil { - http.Error(w, "Failed to exchange token", http.StatusInternalServerError) - return - } - - // Get user info from GitHub - client := githubOauthConfig.Client(context.Background(), token) - resp, err := client.Get("https://api.github.com/user") - if err != nil { - http.Error(w, "Failed to get user info", http.StatusInternalServerError) - return - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - http.Error(w, "Failed to read user info", http.StatusInternalServerError) - return - } - - var user githubUser - if err := json.Unmarshal(body, &user); err != nil { - http.Error(w, "Failed to parse user info", http.StatusInternalServerError) - return - } - - encryptedAccessToken, err := crypto.Encrypt(token.AccessToken, a.config.EncryptionKey) - if err != nil { - http.Error(w, "Failed to encrypt token", http.StatusInternalServerError) - return - } - - var encryptedRefreshToken string - if token.RefreshToken != "" { - encryptedRefreshToken, err = crypto.Encrypt(token.RefreshToken, a.config.EncryptionKey) - if err != nil { - http.Error(w, "Failed to encrypt refresh token", http.StatusInternalServerError) - return - } - } - - params := db.CreateRepoAccountParams{ - TenantID: pgtype.UUID{Bytes: tenantID, Valid: true}, - Provider: string(db.GitProviderGithub), - AccountID: fmt.Sprintf("%d", user.ID), - Username: user.Login, - EncryptedAccessToken: []byte(encryptedAccessToken), - } - - if encryptedRefreshToken != "" { - params.EncryptedRefreshToken = []byte(encryptedRefreshToken) - } - - _, err = a.queries.CreateRepoAccount(context.Background(), params) - - if err != nil { - http.Error(w, "Failed to save account", http.StatusInternalServerError) - return - } - - w.Write([]byte("Successfully connected to GitHub!")) -} - -func (a *API) GithubWebhookHandler(w http.ResponseWriter, r *http.Request) { - // TODO: Implement webhook signature validation - tenantIDStr := r.URL.Query().Get("tenant_id") - tenantID, err := uuid.Parse(tenantIDStr) - if err != nil { - http.Error(w, "Invalid tenant_id", http.StatusBadRequest) - return - } - - payload, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, "Failed to read request body", http.StatusInternalServerError) - return - } - defer r.Body.Close() - - event, err := github.ParseWebHook(github.WebHookType(r), payload) - if err != nil { - http.Error(w, "Could not parse webhook", http.StatusBadRequest) - return - } - - var eventType db.EventType - var repoExternalID string - var repoID pgtype.UUID - - switch e := event.(type) { - case *github.PushEvent: - eventType = db.EventTypePush - repoExternalID = fmt.Sprintf("%d", e.Repo.GetID()) - case *github.PullRequestEvent: - eventType = db.EventTypePullRequest - repoExternalID = fmt.Sprintf("%d", e.Repo.GetID()) - case *github.ReleaseEvent: - eventType = db.EventTypeRelease - repoExternalID = fmt.Sprintf("%d", e.Repo.GetID()) - default: - w.WriteHeader(http.StatusOK) - return - } - - repo, err := a.queries.GetRepositoryByExternalID(r.Context(), db.GetRepositoryByExternalIDParams{ - TenantID: pgtype.UUID{Bytes: tenantID, Valid: true}, - ExternalID: repoExternalID, - }) - if err != nil { - http.Error(w, "Repository not found", http.StatusNotFound) - return - } - repoID = repo.ID - - jsonPayload, err := json.Marshal(event) - if err != nil { - http.Error(w, "Failed to marshal event payload", http.StatusInternalServerError) - return - } - - _, err = a.queries.CreateRepoEvent(r.Context(), db.CreateRepoEventParams{ - TenantID: pgtype.UUID{Bytes: tenantID, Valid: true}, - RepositoryID: repoID, - EventType: string(eventType), - Payload: jsonPayload, - }) - if err != nil { - http.Error(w, "Failed to create repo event", http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) -} \ No newline at end of file diff --git a/repo-integrations-core/internal/api/repositories.go b/repo-integrations-core/internal/api/repositories.go deleted file mode 100644 index feed2b4..0000000 --- a/repo-integrations-core/internal/api/repositories.go +++ /dev/null @@ -1,29 +0,0 @@ -package api - -import ( - "encoding/json" - "net/http" - - "github.com/google/uuid" - "github.com/jackc/pgx/v5/pgtype" -) - -func (a *API) ListRepositoriesHandler(w http.ResponseWriter, r *http.Request) { - tenantIDStr := r.URL.Query().Get("tenant_id") - tenantID, err := uuid.Parse(tenantIDStr) - if err != nil { - http.Error(w, "Invalid tenant_id", http.StatusBadRequest) - return - } - - repos, err := a.queries.ListRepositoriesByTenant(r.Context(), pgtype.UUID{Bytes: tenantID, Valid: true}) - if err != nil { - http.Error(w, "Failed to list repositories", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(repos); err != nil { - http.Error(w, "Failed to encode response", http.StatusInternalServerError) - } -} diff --git a/repo-integrations-core/internal/config/config.go b/repo-integrations-core/internal/config/config.go deleted file mode 100644 index e0766e5..0000000 --- a/repo-integrations-core/internal/config/config.go +++ /dev/null @@ -1,41 +0,0 @@ -package config - -import ( - "log" - "os" - - "github.com/joho/godotenv" -) - -type Config struct { - DatabaseURL string - JWTSecret string - EncryptionKey string - GithubClientID string - GithubSecret string -} - -func Load() *Config { - err := godotenv.Load() - if err != nil { - log.Println("No .env file found, using environment variables") - } - - return &Config{ - DatabaseURL: getEnv("DATABASE_URL", ""), - JWTSecret: getEnv("JWT_SECRET", ""), - EncryptionKey: getEnv("ENCRYPTION_KEY", ""), - GithubClientID: getEnv("GITHUB_CLIENT_ID", ""), - GithubSecret: getEnv("GITHUB_SECRET", ""), - } -} - -func getEnv(key, fallback string) string { - if value, ok := os.LookupEnv(key); ok { - return value - } - if fallback == "" { - log.Fatalf("FATAL: Environment variable %s is not set.", key) - } - return fallback -} diff --git a/repo-integrations-core/internal/crypto/crypto.go b/repo-integrations-core/internal/crypto/crypto.go deleted file mode 100644 index 592b216..0000000 --- a/repo-integrations-core/internal/crypto/crypto.go +++ /dev/null @@ -1,63 +0,0 @@ -package crypto - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/hex" - "fmt" - "io" -) - -// Encrypt encrypts data using AES-GCM. -func Encrypt(stringToEncrypt string, keyString string) (string, error) { - key, _ := hex.DecodeString(keyString) - plaintext := []byte(stringToEncrypt) - - block, err := aes.NewCipher(key) - if err != nil { - return "", err - } - - aesGCM, err := cipher.NewGCM(block) - if err != nil { - return "", err - } - - nonce := make([]byte, aesGCM.NonceSize()) - if _, err = io.ReadFull(rand.Reader, nonce); err != nil { - return "", err - } - - ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil) - return fmt.Sprintf("%x", ciphertext), nil -} - -// Decrypt decrypts data using AES-GCM. -func Decrypt(encryptedString string, keyString string) (string, error) { - key, _ := hex.DecodeString(keyString) - enc, _ := hex.DecodeString(encryptedString) - - block, err := aes.NewCipher(key) - if err != nil { - return "", err - } - - aesGCM, err := cipher.NewGCM(block) - if err != nil { - return "", err - } - - nonceSize := aesGCM.NonceSize() - if len(enc) < nonceSize { - return "", fmt.Errorf("ciphertext too short") - } - - nonce, ciphertext := enc[:nonceSize], enc[nonceSize:] - plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil) - if err != nil { - return "", err - } - - return string(plaintext), nil -} diff --git a/repo-integrations-core/internal/db/db.go b/repo-integrations-core/internal/db/db.go deleted file mode 100644 index 9d485b5..0000000 --- a/repo-integrations-core/internal/db/db.go +++ /dev/null @@ -1,32 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 - -package db - -import ( - "context" - - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" -) - -type DBTX interface { - Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) - Query(context.Context, string, ...interface{}) (pgx.Rows, error) - QueryRow(context.Context, string, ...interface{}) pgx.Row -} - -func New(db DBTX) *Queries { - return &Queries{db: db} -} - -type Queries struct { - db DBTX -} - -func (q *Queries) WithTx(tx pgx.Tx) *Queries { - return &Queries{ - db: tx, - } -} diff --git a/repo-integrations-core/internal/db/models.go b/repo-integrations-core/internal/db/models.go deleted file mode 100644 index d8c9126..0000000 --- a/repo-integrations-core/internal/db/models.go +++ /dev/null @@ -1,142 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 - -package db - -import ( - "database/sql/driver" - "fmt" - - "github.com/jackc/pgx/v5/pgtype" -) - -type EventType string - -const ( - EventTypePush EventType = "push" - EventTypePullRequest EventType = "pull_request" - EventTypeRelease EventType = "release" -) - -func (e *EventType) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = EventType(s) - case string: - *e = EventType(s) - default: - return fmt.Errorf("unsupported scan type for EventType: %T", src) - } - return nil -} - -type NullEventType struct { - EventType EventType `json:"event_type"` - Valid bool `json:"valid"` // Valid is true if EventType is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullEventType) Scan(value interface{}) error { - if value == nil { - ns.EventType, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.EventType.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullEventType) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.EventType), nil -} - -type GitProvider string - -const ( - GitProviderGithub GitProvider = "github" - GitProviderGitlab GitProvider = "gitlab" - GitProviderBitbucket GitProvider = "bitbucket" -) - -func (e *GitProvider) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = GitProvider(s) - case string: - *e = GitProvider(s) - default: - return fmt.Errorf("unsupported scan type for GitProvider: %T", src) - } - return nil -} - -type NullGitProvider struct { - GitProvider GitProvider `json:"git_provider"` - Valid bool `json:"valid"` // Valid is true if GitProvider is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullGitProvider) Scan(value interface{}) error { - if value == nil { - ns.GitProvider, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.GitProvider.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullGitProvider) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.GitProvider), nil -} - -type RepoAccount struct { - ID pgtype.UUID `json:"id"` - TenantID pgtype.UUID `json:"tenant_id"` - Provider string `json:"provider"` - AccountID string `json:"account_id"` - Username string `json:"username"` - EncryptedAccessToken []byte `json:"encrypted_access_token"` - EncryptedRefreshToken []byte `json:"encrypted_refresh_token"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` -} - -type RepoEvent struct { - ID pgtype.UUID `json:"id"` - TenantID pgtype.UUID `json:"tenant_id"` - RepositoryID pgtype.UUID `json:"repository_id"` - EventType string `json:"event_type"` - Payload []byte `json:"payload"` - ProcessedAt pgtype.Timestamptz `json:"processed_at"` - CreatedAt pgtype.Timestamptz `json:"created_at"` -} - -type Repository struct { - ID pgtype.UUID `json:"id"` - TenantID pgtype.UUID `json:"tenant_id"` - RepoAccountID pgtype.UUID `json:"repo_account_id"` - ExternalID string `json:"external_id"` - Name string `json:"name"` - Url string `json:"url"` - IsActive bool `json:"is_active"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` -} - -type Webhook struct { - ID pgtype.UUID `json:"id"` - TenantID pgtype.UUID `json:"tenant_id"` - RepositoryID pgtype.UUID `json:"repository_id"` - ExternalID string `json:"external_id"` - Secret string `json:"secret"` - IsActive bool `json:"is_active"` - CreatedAt pgtype.Timestamptz `json:"created_at"` -} diff --git a/repo-integrations-core/internal/db/querier.go b/repo-integrations-core/internal/db/querier.go deleted file mode 100644 index 47a4282..0000000 --- a/repo-integrations-core/internal/db/querier.go +++ /dev/null @@ -1,29 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 - -package db - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -type Querier interface { - CreateRepoAccount(ctx context.Context, arg CreateRepoAccountParams) (RepoAccount, error) - CreateRepoEvent(ctx context.Context, arg CreateRepoEventParams) (RepoEvent, error) - CreateRepository(ctx context.Context, arg CreateRepositoryParams) (Repository, error) - CreateWebhook(ctx context.Context, arg CreateWebhookParams) (Webhook, error) - GetRepoAccount(ctx context.Context, arg GetRepoAccountParams) (RepoAccount, error) - GetRepoAccountByID(ctx context.Context, arg GetRepoAccountByIDParams) (RepoAccount, error) - GetRepository(ctx context.Context, arg GetRepositoryParams) (Repository, error) - GetRepositoryByExternalID(ctx context.Context, arg GetRepositoryByExternalIDParams) (Repository, error) - GetWebhookByRepoExternalID(ctx context.Context, arg GetWebhookByRepoExternalIDParams) (Webhook, error) - GetWebhookByRepoID(ctx context.Context, arg GetWebhookByRepoIDParams) (Webhook, error) - ListRepoAccountsByTenant(ctx context.Context, tenantID pgtype.UUID) ([]RepoAccount, error) - ListRepositoriesByTenant(ctx context.Context, tenantID pgtype.UUID) ([]Repository, error) - UpdateRepoAccountTokens(ctx context.Context, arg UpdateRepoAccountTokensParams) (RepoAccount, error) -} - -var _ Querier = (*Queries)(nil) diff --git a/repo-integrations-core/internal/db/query.sql.go b/repo-integrations-core/internal/db/query.sql.go deleted file mode 100644 index 38571eb..0000000 --- a/repo-integrations-core/internal/db/query.sql.go +++ /dev/null @@ -1,446 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: query.sql - -package db - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const createRepoAccount = `-- name: CreateRepoAccount :one -INSERT INTO repo_accounts ( - tenant_id, - provider, - account_id, - username, - encrypted_access_token, - encrypted_refresh_token -) VALUES ( - $1, $2, $3, $4, $5, $6 -) RETURNING id, tenant_id, provider, account_id, username, encrypted_access_token, encrypted_refresh_token, created_at, updated_at -` - -type CreateRepoAccountParams struct { - TenantID pgtype.UUID `json:"tenant_id"` - Provider string `json:"provider"` - AccountID string `json:"account_id"` - Username string `json:"username"` - EncryptedAccessToken []byte `json:"encrypted_access_token"` - EncryptedRefreshToken []byte `json:"encrypted_refresh_token"` -} - -func (q *Queries) CreateRepoAccount(ctx context.Context, arg CreateRepoAccountParams) (RepoAccount, error) { - row := q.db.QueryRow(ctx, createRepoAccount, - arg.TenantID, - arg.Provider, - arg.AccountID, - arg.Username, - arg.EncryptedAccessToken, - arg.EncryptedRefreshToken, - ) - var i RepoAccount - err := row.Scan( - &i.ID, - &i.TenantID, - &i.Provider, - &i.AccountID, - &i.Username, - &i.EncryptedAccessToken, - &i.EncryptedRefreshToken, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const createRepoEvent = `-- name: CreateRepoEvent :one -INSERT INTO repo_events ( - tenant_id, - repository_id, - event_type, - payload -) VALUES ( - $1, $2, $3, $4 -) RETURNING id, tenant_id, repository_id, event_type, payload, processed_at, created_at -` - -type CreateRepoEventParams struct { - TenantID pgtype.UUID `json:"tenant_id"` - RepositoryID pgtype.UUID `json:"repository_id"` - EventType string `json:"event_type"` - Payload []byte `json:"payload"` -} - -func (q *Queries) CreateRepoEvent(ctx context.Context, arg CreateRepoEventParams) (RepoEvent, error) { - row := q.db.QueryRow(ctx, createRepoEvent, - arg.TenantID, - arg.RepositoryID, - arg.EventType, - arg.Payload, - ) - var i RepoEvent - err := row.Scan( - &i.ID, - &i.TenantID, - &i.RepositoryID, - &i.EventType, - &i.Payload, - &i.ProcessedAt, - &i.CreatedAt, - ) - return i, err -} - -const createRepository = `-- name: CreateRepository :one -INSERT INTO repositories ( - tenant_id, - repo_account_id, - external_id, - name, - url -) VALUES ( - $1, $2, $3, $4, $5 -) RETURNING id, tenant_id, repo_account_id, external_id, name, url, is_active, created_at, updated_at -` - -type CreateRepositoryParams struct { - TenantID pgtype.UUID `json:"tenant_id"` - RepoAccountID pgtype.UUID `json:"repo_account_id"` - ExternalID string `json:"external_id"` - Name string `json:"name"` - Url string `json:"url"` -} - -func (q *Queries) CreateRepository(ctx context.Context, arg CreateRepositoryParams) (Repository, error) { - row := q.db.QueryRow(ctx, createRepository, - arg.TenantID, - arg.RepoAccountID, - arg.ExternalID, - arg.Name, - arg.Url, - ) - var i Repository - err := row.Scan( - &i.ID, - &i.TenantID, - &i.RepoAccountID, - &i.ExternalID, - &i.Name, - &i.Url, - &i.IsActive, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const createWebhook = `-- name: CreateWebhook :one -INSERT INTO webhooks ( - tenant_id, - repository_id, - external_id, - secret -) VALUES ( - $1, $2, $3, $4 -) RETURNING id, tenant_id, repository_id, external_id, secret, is_active, created_at -` - -type CreateWebhookParams struct { - TenantID pgtype.UUID `json:"tenant_id"` - RepositoryID pgtype.UUID `json:"repository_id"` - ExternalID string `json:"external_id"` - Secret string `json:"secret"` -} - -func (q *Queries) CreateWebhook(ctx context.Context, arg CreateWebhookParams) (Webhook, error) { - row := q.db.QueryRow(ctx, createWebhook, - arg.TenantID, - arg.RepositoryID, - arg.ExternalID, - arg.Secret, - ) - var i Webhook - err := row.Scan( - &i.ID, - &i.TenantID, - &i.RepositoryID, - &i.ExternalID, - &i.Secret, - &i.IsActive, - &i.CreatedAt, - ) - return i, err -} - -const getRepoAccount = `-- name: GetRepoAccount :one -SELECT id, tenant_id, provider, account_id, username, encrypted_access_token, encrypted_refresh_token, created_at, updated_at FROM repo_accounts -WHERE tenant_id = $1 AND provider = $2 AND account_id = $3 -` - -type GetRepoAccountParams struct { - TenantID pgtype.UUID `json:"tenant_id"` - Provider string `json:"provider"` - AccountID string `json:"account_id"` -} - -func (q *Queries) GetRepoAccount(ctx context.Context, arg GetRepoAccountParams) (RepoAccount, error) { - row := q.db.QueryRow(ctx, getRepoAccount, arg.TenantID, arg.Provider, arg.AccountID) - var i RepoAccount - err := row.Scan( - &i.ID, - &i.TenantID, - &i.Provider, - &i.AccountID, - &i.Username, - &i.EncryptedAccessToken, - &i.EncryptedRefreshToken, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const getRepoAccountByID = `-- name: GetRepoAccountByID :one -SELECT id, tenant_id, provider, account_id, username, encrypted_access_token, encrypted_refresh_token, created_at, updated_at FROM repo_accounts -WHERE tenant_id = $1 AND id = $2 -` - -type GetRepoAccountByIDParams struct { - TenantID pgtype.UUID `json:"tenant_id"` - ID pgtype.UUID `json:"id"` -} - -func (q *Queries) GetRepoAccountByID(ctx context.Context, arg GetRepoAccountByIDParams) (RepoAccount, error) { - row := q.db.QueryRow(ctx, getRepoAccountByID, arg.TenantID, arg.ID) - var i RepoAccount - err := row.Scan( - &i.ID, - &i.TenantID, - &i.Provider, - &i.AccountID, - &i.Username, - &i.EncryptedAccessToken, - &i.EncryptedRefreshToken, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const getRepository = `-- name: GetRepository :one -SELECT id, tenant_id, repo_account_id, external_id, name, url, is_active, created_at, updated_at FROM repositories -WHERE tenant_id = $1 AND id = $2 -` - -type GetRepositoryParams struct { - TenantID pgtype.UUID `json:"tenant_id"` - ID pgtype.UUID `json:"id"` -} - -func (q *Queries) GetRepository(ctx context.Context, arg GetRepositoryParams) (Repository, error) { - row := q.db.QueryRow(ctx, getRepository, arg.TenantID, arg.ID) - var i Repository - err := row.Scan( - &i.ID, - &i.TenantID, - &i.RepoAccountID, - &i.ExternalID, - &i.Name, - &i.Url, - &i.IsActive, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const getRepositoryByExternalID = `-- name: GetRepositoryByExternalID :one -SELECT id, tenant_id, repo_account_id, external_id, name, url, is_active, created_at, updated_at FROM repositories -WHERE tenant_id = $1 AND external_id = $2 -` - -type GetRepositoryByExternalIDParams struct { - TenantID pgtype.UUID `json:"tenant_id"` - ExternalID string `json:"external_id"` -} - -func (q *Queries) GetRepositoryByExternalID(ctx context.Context, arg GetRepositoryByExternalIDParams) (Repository, error) { - row := q.db.QueryRow(ctx, getRepositoryByExternalID, arg.TenantID, arg.ExternalID) - var i Repository - err := row.Scan( - &i.ID, - &i.TenantID, - &i.RepoAccountID, - &i.ExternalID, - &i.Name, - &i.Url, - &i.IsActive, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const getWebhookByRepoExternalID = `-- name: GetWebhookByRepoExternalID :one -SELECT w.id, w.tenant_id, w.repository_id, w.external_id, w.secret, w.is_active, w.created_at FROM webhooks w -JOIN repositories r ON w.repository_id = r.id -WHERE r.tenant_id = $1 AND r.external_id = $2 -` - -type GetWebhookByRepoExternalIDParams struct { - TenantID pgtype.UUID `json:"tenant_id"` - ExternalID string `json:"external_id"` -} - -func (q *Queries) GetWebhookByRepoExternalID(ctx context.Context, arg GetWebhookByRepoExternalIDParams) (Webhook, error) { - row := q.db.QueryRow(ctx, getWebhookByRepoExternalID, arg.TenantID, arg.ExternalID) - var i Webhook - err := row.Scan( - &i.ID, - &i.TenantID, - &i.RepositoryID, - &i.ExternalID, - &i.Secret, - &i.IsActive, - &i.CreatedAt, - ) - return i, err -} - -const getWebhookByRepoID = `-- name: GetWebhookByRepoID :one -SELECT id, tenant_id, repository_id, external_id, secret, is_active, created_at FROM webhooks -WHERE tenant_id = $1 AND repository_id = $2 -` - -type GetWebhookByRepoIDParams struct { - TenantID pgtype.UUID `json:"tenant_id"` - RepositoryID pgtype.UUID `json:"repository_id"` -} - -func (q *Queries) GetWebhookByRepoID(ctx context.Context, arg GetWebhookByRepoIDParams) (Webhook, error) { - row := q.db.QueryRow(ctx, getWebhookByRepoID, arg.TenantID, arg.RepositoryID) - var i Webhook - err := row.Scan( - &i.ID, - &i.TenantID, - &i.RepositoryID, - &i.ExternalID, - &i.Secret, - &i.IsActive, - &i.CreatedAt, - ) - return i, err -} - -const listRepoAccountsByTenant = `-- name: ListRepoAccountsByTenant :many -SELECT id, tenant_id, provider, account_id, username, encrypted_access_token, encrypted_refresh_token, created_at, updated_at FROM repo_accounts -WHERE tenant_id = $1 -` - -func (q *Queries) ListRepoAccountsByTenant(ctx context.Context, tenantID pgtype.UUID) ([]RepoAccount, error) { - rows, err := q.db.Query(ctx, listRepoAccountsByTenant, tenantID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []RepoAccount - for rows.Next() { - var i RepoAccount - if err := rows.Scan( - &i.ID, - &i.TenantID, - &i.Provider, - &i.AccountID, - &i.Username, - &i.EncryptedAccessToken, - &i.EncryptedRefreshToken, - &i.CreatedAt, - &i.UpdatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const listRepositoriesByTenant = `-- name: ListRepositoriesByTenant :many -SELECT id, tenant_id, repo_account_id, external_id, name, url, is_active, created_at, updated_at FROM repositories -WHERE tenant_id = $1 AND is_active = TRUE -` - -func (q *Queries) ListRepositoriesByTenant(ctx context.Context, tenantID pgtype.UUID) ([]Repository, error) { - rows, err := q.db.Query(ctx, listRepositoriesByTenant, tenantID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Repository - for rows.Next() { - var i Repository - if err := rows.Scan( - &i.ID, - &i.TenantID, - &i.RepoAccountID, - &i.ExternalID, - &i.Name, - &i.Url, - &i.IsActive, - &i.CreatedAt, - &i.UpdatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const updateRepoAccountTokens = `-- name: UpdateRepoAccountTokens :one -UPDATE repo_accounts -SET - encrypted_access_token = $3, - encrypted_refresh_token = $4, - updated_at = NOW() -WHERE tenant_id = $1 AND id = $2 -RETURNING id, tenant_id, provider, account_id, username, encrypted_access_token, encrypted_refresh_token, created_at, updated_at -` - -type UpdateRepoAccountTokensParams struct { - TenantID pgtype.UUID `json:"tenant_id"` - ID pgtype.UUID `json:"id"` - EncryptedAccessToken []byte `json:"encrypted_access_token"` - EncryptedRefreshToken []byte `json:"encrypted_refresh_token"` -} - -func (q *Queries) UpdateRepoAccountTokens(ctx context.Context, arg UpdateRepoAccountTokensParams) (RepoAccount, error) { - row := q.db.QueryRow(ctx, updateRepoAccountTokens, - arg.TenantID, - arg.ID, - arg.EncryptedAccessToken, - arg.EncryptedRefreshToken, - ) - var i RepoAccount - err := row.Scan( - &i.ID, - &i.TenantID, - &i.Provider, - &i.AccountID, - &i.Username, - &i.EncryptedAccessToken, - &i.EncryptedRefreshToken, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} diff --git a/repo-integrations-core/migrations/000001_init_schema.up.sql b/repo-integrations-core/migrations/000001_init_schema.up.sql deleted file mode 100644 index b7824f7..0000000 --- a/repo-integrations-core/migrations/000001_init_schema.up.sql +++ /dev/null @@ -1,65 +0,0 @@ --- +migrate Up -CREATE TYPE git_provider AS ENUM ('github', 'gitlab', 'bitbucket'); -CREATE TYPE event_type AS ENUM ('push', 'pull_request', 'release'); - -CREATE TABLE repo_accounts ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - provider git_provider NOT NULL, - account_id VARCHAR(255) NOT NULL, - username VARCHAR(255) NOT NULL, - encrypted_access_token BYTEA NOT NULL, - encrypted_refresh_token BYTEA, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - UNIQUE(tenant_id, provider, account_id) -); - -CREATE TABLE repositories ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - repo_account_id UUID NOT NULL REFERENCES repo_accounts(id) ON DELETE CASCADE, - external_id VARCHAR(255) NOT NULL, - name VARCHAR(255) NOT NULL, - url VARCHAR(255) NOT NULL, - is_active BOOLEAN NOT NULL DEFAULT TRUE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - UNIQUE(tenant_id, external_id) -); - -CREATE TABLE webhooks ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - repository_id UUID NOT NULL REFERENCES repositories(id) ON DELETE CASCADE, - external_id VARCHAR(255) NOT NULL, - secret TEXT NOT NULL, - is_active BOOLEAN NOT NULL DEFAULT TRUE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - UNIQUE(tenant_id, repository_id) -); - -CREATE TABLE repo_events ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - repository_id UUID NOT NULL REFERENCES repositories(id), - event_type event_type NOT NULL, - payload JSONB NOT NULL, - processed_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - --- Indexes -CREATE INDEX ON repo_accounts (tenant_id); -CREATE INDEX ON repositories (tenant_id, repo_account_id); -CREATE INDEX ON webhooks (tenant_id, repository_id); -CREATE INDEX ON repo_events (tenant_id, repository_id, event_type); - - --- +migrate Down -DROP TABLE IF EXISTS repo_events; -DROP TABLE IF EXISTS webhooks; -DROP TABLE IF EXISTS repositories; -DROP TABLE IF EXISTS repo_accounts; -DROP TYPE IF EXISTS event_type; -DROP TYPE IF EXISTS git_provider; diff --git a/repo-integrations-core/sqlc.yaml b/repo-integrations-core/sqlc.yaml deleted file mode 100644 index 37ba14c..0000000 --- a/repo-integrations-core/sqlc.yaml +++ /dev/null @@ -1,19 +0,0 @@ -version: "2" -sql: - - engine: "postgresql" - schema: "migrations" - queries: "db/query.sql" - gen: - go: - package: "db" - out: "internal/db" - sql_package: "pgx/v5" - emit_json_tags: true - emit_interface: true - overrides: - - db_type: "pg_catalog.uuid" - go_type: "github.com/google/uuid.UUID" - - db_type: "event_type" - go_type: "string" - - db_type: "git_provider" - go_type: "string" diff --git a/security-governance-core/.dockerignore b/security-governance-core/.dockerignore deleted file mode 100644 index 94eb047..0000000 --- a/security-governance-core/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -.git -.env -.gitignore -Dockerfile -README.md -SECURITY-GOVERNANCE-CORE.md -migrations -*.log diff --git a/security-governance-core/.env.example b/security-governance-core/.env.example deleted file mode 100644 index 097e96f..0000000 --- a/security-governance-core/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -DATABASE_URL= -PORT=8080 diff --git a/security-governance-core/.gitignore b/security-governance-core/.gitignore deleted file mode 100644 index 8564984..0000000 --- a/security-governance-core/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.env -*.log -.DS_Store -security-governance-core -main -coverage diff --git a/security-governance-core/Dockerfile b/security-governance-core/Dockerfile deleted file mode 100644 index 35cd46b..0000000 --- a/security-governance-core/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -# Dockerfile -FROM docker.io/library/golang:1.23-alpine AS builder - -WORKDIR /app - -COPY go.mod go.sum ./ -RUN go mod download - -COPY . . - -# Build with optimization flags -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /app/security-governance-core ./cmd/api - -# Use Google Distroless static image for minimal size and security -FROM gcr.io/distroless/static:nonroot - -WORKDIR /app - -COPY --from=builder /app/security-governance-core . - -USER nonroot:nonroot - -EXPOSE 8080 - -CMD ["./security-governance-core"] diff --git a/security-governance-core/SECURITY-GOVERNANCE-CORE.md b/security-governance-core/SECURITY-GOVERNANCE-CORE.md deleted file mode 100644 index 7a17129..0000000 --- a/security-governance-core/SECURITY-GOVERNANCE-CORE.md +++ /dev/null @@ -1,71 +0,0 @@ -# SECURITY-GOVERNANCE-CORE - -O `security-governance-core` é o guardião das políticas de segurança e conformidade da plataforma. - -## 📋 Visão Geral - -Este serviço centraliza a governança, garantindo que todos os tenants operem dentro dos parâmetros de segurança definidos. Ele mantém um registro de auditoria imutável e gerencia perfis de risco. - -### Arquitetura - -```mermaid -graph TD - Service[Any Service] -->|Event| Queue[Audit Queue] - Queue -->|Consume| Worker[Audit Worker] - Worker -->|Store| DB[(Audit Log DB)] - - Admin[Compliance Officer] -->|Define| Policy[Security Policy] - Policy -->|Enforce| Gatekeeper[Policy Engine] -``` - -## 🚀 Estrutura do Projeto - -O projeto é escrito em **Go** e foca em imutabilidade e rastreabilidade: - -| Diretório | Descrição | -| :--- | :--- | -| `cmd/api` | API de gestão. | -| `internal/audit` | Ingestão de logs de auditoria. | -| `internal/policies` | Motor de políticas (OPA-like simple engine). | -| `internal/compliance` | Checklists e relatórios. | - -## 🛠️ Tecnologias e Otimizações - -- **Linguagem**: Go 1.23. -- **Database**: PostgreSQL (Tabelas *append-only* para auditoria). -- **Containerização**: - - Base `distroless/static`. - - Segurança máxima (sem shell). - -## 💻 Como Executar - -### Docker (Recomendado) - -```bash -# Build -docker build -t security-governance-core . - -# Run -docker run -p 8080:8080 --env-file .env security-governance-core -``` - -### Desenvolvimento - -1. **Dependências**: Go 1.23+, Postgres. -2. **Setup**: - ```bash - cp .env.example .env - go mod tidy - ``` -3. **Executar**: - ```bash - go run ./cmd/api - ``` - -## 🔧 Detalhes do Dockerfile - -O `Dockerfile` é endurecido para ambientes regulados: - -- **Base**: Distroless Static. -- **User**: Non-root (UID 65532). -- **Network**: Apenas portas necessárias expostas. diff --git a/security-governance-core/cmd/api/main.go b/security-governance-core/cmd/api/main.go deleted file mode 100644 index 8c94719..0000000 --- a/security-governance-core/cmd/api/main.go +++ /dev/null @@ -1,98 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "log" - "net/http" - "strconv" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "github.com/jackc/pgx/v5/pgxpool" - "github.com/lab/security-governance-core/internal/audit" - "github.com/lab/security-governance-core/internal/config" - "github.com/lab/security-governance-core/internal/db" -) - -type application struct { - config *config.Config - queries *db.Queries - auditLogger *audit.Logger -} - -func main() { - cfg := config.Load() - - pool, err := pgxpool.New(context.Background(), cfg.DatabaseURL) - if err != nil { - log.Fatalf("Unable to connect to database: %v\n", err) - } - defer pool.Close() - - queries := db.New(pool) - app := &application{ - config: cfg, - queries: queries, - auditLogger: audit.NewLogger(queries), - } - - r := chi.NewRouter() - r.Use(middleware.Logger) - r.Use(middleware.Recoverer) - - r.Get("/", func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Security Governance Core")) - }) - - r.Post("/security-profiles", app.createSecurityProfileHandler) - r.Post("/policies", app.createPolicyHandler) - r.Get("/findings", app.getFindingsHandler) - r.Get("/audit-logs", app.getAuditLogsHandler) - - log.Printf("Starting server on port %s", cfg.Port) - if err := http.ListenAndServe(fmt.Sprintf(":%s", cfg.Port), r); err != nil { - log.Fatalf("Could not start server: %s\n", err) - } -} - -func (app *application) createSecurityProfileHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - -func (app *application) createPolicyHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - -func (app *application) getFindingsHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - -func (app *application) getAuditLogsHandler(w http.ResponseWriter, r *http.Request) { - limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) - offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) - - if limit <= 0 { - limit = 10 - } - if offset < 0 { - offset = 0 - } - - // For demonstration, we'll use a hardcoded actor ID. In a real app, this would come from a JWT token. - actorID := "user-123" - app.auditLogger.Log(r.Context(), actorID, "list_audit_logs", "audit_log", "", nil) - - logs, err := app.queries.ListAuditLogs(r.Context(), db.ListAuditLogsParams{ - Limit: int32(limit), - Offset: int32(offset), - }) - if err != nil { - http.Error(w, "Failed to retrieve audit logs", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(logs) -} diff --git a/security-governance-core/db/query.sql b/security-governance-core/db/query.sql deleted file mode 100644 index 3597881..0000000 --- a/security-governance-core/db/query.sql +++ /dev/null @@ -1,33 +0,0 @@ --- name: CreateSecurityProfile :one -INSERT INTO security_profiles (name, risk_level) -VALUES ($1, $2) -RETURNING *; - --- name: CreatePolicy :one -INSERT INTO policies (name, description) -VALUES ($1, $2) -RETURNING *; - --- name: CreateControl :one -INSERT INTO controls (policy_id, name, description) -VALUES ($1, $2, $3) -RETURNING *; - --- name: CreateFinding :one -INSERT INTO findings (control_id, resource_id, status, details) -VALUES ($1, $2, $3, $4) -RETURNING *; - --- name: ListFindings :many -SELECT * FROM findings -WHERE status = $1; - --- name: CreateAuditLog :one -INSERT INTO audit_logs (actor_id, action, resource_type, resource_id, details) -VALUES ($1, $2, $3, $4, $5) -RETURNING *; - --- name: ListAuditLogs :many -SELECT * FROM audit_logs -ORDER BY timestamp DESC -LIMIT $1 OFFSET $2; diff --git a/security-governance-core/go.mod b/security-governance-core/go.mod deleted file mode 100644 index 53a332f..0000000 --- a/security-governance-core/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/lab/security-governance-core - -go 1.23 - -require ( - github.com/go-chi/chi/v5 v5.2.3 - github.com/jackc/pgx/v5 v5.7.1 - github.com/joho/godotenv v1.5.1 -) - -require ( - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/text v0.21.0 // indirect -) diff --git a/security-governance-core/go.sum b/security-governance-core/go.sum deleted file mode 100644 index afb5e49..0000000 --- a/security-governance-core/go.sum +++ /dev/null @@ -1,32 +0,0 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= -github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/security-governance-core/internal/audit/audit.go b/security-governance-core/internal/audit/audit.go deleted file mode 100644 index c5f9de4..0000000 --- a/security-governance-core/internal/audit/audit.go +++ /dev/null @@ -1,34 +0,0 @@ -package audit - -import ( - "context" - "encoding/json" - - "github.com/jackc/pgx/v5/pgtype" - "github.com/lab/security-governance-core/internal/db" -) - -type Logger struct { - queries *db.Queries -} - -func NewLogger(queries *db.Queries) *Logger { - return &Logger{queries: queries} -} - -func (l *Logger) Log(ctx context.Context, actorID, action, resourceType, resourceID string, details interface{}) error { - detailsJSON, err := json.Marshal(details) - if err != nil { - return err - } - - _, err = l.queries.CreateAuditLog(ctx, db.CreateAuditLogParams{ - ActorID: actorID, - Action: action, - ResourceType: pgtype.Text{String: resourceType, Valid: resourceType != ""}, - ResourceID: pgtype.Text{String: resourceID, Valid: resourceID != ""}, - Details: detailsJSON, - }) - - return err -} diff --git a/security-governance-core/internal/config/config.go b/security-governance-core/internal/config/config.go deleted file mode 100644 index f1066bd..0000000 --- a/security-governance-core/internal/config/config.go +++ /dev/null @@ -1,35 +0,0 @@ -package config - -import ( - "log" - "os" - - "github.com/joho/godotenv" -) - -type Config struct { - DatabaseURL string - Port string -} - -func Load() *Config { - err := godotenv.Load() - if err != nil { - log.Println("No .env file found, using environment variables") - } - - return &Config{ - DatabaseURL: getEnv("DATABASE_URL", ""), - Port: getEnv("PORT", "8080"), - } -} - -func getEnv(key, fallback string) string { - if value, ok := os.LookupEnv(key); ok { - return value - } - if fallback == "" { - log.Fatalf("FATAL: Environment variable %s is not set.", key) - } - return fallback -} diff --git a/security-governance-core/internal/db/db.go b/security-governance-core/internal/db/db.go deleted file mode 100644 index 9d485b5..0000000 --- a/security-governance-core/internal/db/db.go +++ /dev/null @@ -1,32 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 - -package db - -import ( - "context" - - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" -) - -type DBTX interface { - Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) - Query(context.Context, string, ...interface{}) (pgx.Rows, error) - QueryRow(context.Context, string, ...interface{}) pgx.Row -} - -func New(db DBTX) *Queries { - return &Queries{db: db} -} - -type Queries struct { - db DBTX -} - -func (q *Queries) WithTx(tx pgx.Tx) *Queries { - return &Queries{ - db: tx, - } -} diff --git a/security-governance-core/internal/db/models.go b/security-governance-core/internal/db/models.go deleted file mode 100644 index 94f290d..0000000 --- a/security-governance-core/internal/db/models.go +++ /dev/null @@ -1,144 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 - -package db - -import ( - "database/sql/driver" - "fmt" - - "github.com/jackc/pgx/v5/pgtype" -) - -type FindingStatus string - -const ( - FindingStatusOpen FindingStatus = "open" - FindingStatusResolved FindingStatus = "resolved" - FindingStatusIgnored FindingStatus = "ignored" -) - -func (e *FindingStatus) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = FindingStatus(s) - case string: - *e = FindingStatus(s) - default: - return fmt.Errorf("unsupported scan type for FindingStatus: %T", src) - } - return nil -} - -type NullFindingStatus struct { - FindingStatus FindingStatus `json:"finding_status"` - Valid bool `json:"valid"` // Valid is true if FindingStatus is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullFindingStatus) Scan(value interface{}) error { - if value == nil { - ns.FindingStatus, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.FindingStatus.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullFindingStatus) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.FindingStatus), nil -} - -type RiskLevel string - -const ( - RiskLevelLow RiskLevel = "low" - RiskLevelMedium RiskLevel = "medium" - RiskLevelHigh RiskLevel = "high" - RiskLevelCritical RiskLevel = "critical" -) - -func (e *RiskLevel) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = RiskLevel(s) - case string: - *e = RiskLevel(s) - default: - return fmt.Errorf("unsupported scan type for RiskLevel: %T", src) - } - return nil -} - -type NullRiskLevel struct { - RiskLevel RiskLevel `json:"risk_level"` - Valid bool `json:"valid"` // Valid is true if RiskLevel is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullRiskLevel) Scan(value interface{}) error { - if value == nil { - ns.RiskLevel, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.RiskLevel.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullRiskLevel) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.RiskLevel), nil -} - -type AuditLog struct { - ID int64 `json:"id"` - Timestamp pgtype.Timestamptz `json:"timestamp"` - ActorID string `json:"actor_id"` - Action string `json:"action"` - ResourceType pgtype.Text `json:"resource_type"` - ResourceID pgtype.Text `json:"resource_id"` - Details []byte `json:"details"` -} - -type Control struct { - ID pgtype.UUID `json:"id"` - PolicyID pgtype.UUID `json:"policy_id"` - Name string `json:"name"` - Description pgtype.Text `json:"description"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` -} - -type Finding struct { - ID pgtype.UUID `json:"id"` - ControlID pgtype.UUID `json:"control_id"` - ResourceID string `json:"resource_id"` - Status FindingStatus `json:"status"` - Details []byte `json:"details"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` -} - -type Policy struct { - ID pgtype.UUID `json:"id"` - Name string `json:"name"` - Description pgtype.Text `json:"description"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` -} - -type SecurityProfile struct { - ID pgtype.UUID `json:"id"` - Name string `json:"name"` - RiskLevel RiskLevel `json:"risk_level"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` -} diff --git a/security-governance-core/internal/db/query.sql.go b/security-governance-core/internal/db/query.sql.go deleted file mode 100644 index ed0cf0b..0000000 --- a/security-governance-core/internal/db/query.sql.go +++ /dev/null @@ -1,226 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: query.sql - -package db - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const createAuditLog = `-- name: CreateAuditLog :one -INSERT INTO audit_logs (actor_id, action, resource_type, resource_id, details) -VALUES ($1, $2, $3, $4, $5) -RETURNING id, timestamp, actor_id, action, resource_type, resource_id, details -` - -type CreateAuditLogParams struct { - ActorID string `json:"actor_id"` - Action string `json:"action"` - ResourceType pgtype.Text `json:"resource_type"` - ResourceID pgtype.Text `json:"resource_id"` - Details []byte `json:"details"` -} - -func (q *Queries) CreateAuditLog(ctx context.Context, arg CreateAuditLogParams) (AuditLog, error) { - row := q.db.QueryRow(ctx, createAuditLog, - arg.ActorID, - arg.Action, - arg.ResourceType, - arg.ResourceID, - arg.Details, - ) - var i AuditLog - err := row.Scan( - &i.ID, - &i.Timestamp, - &i.ActorID, - &i.Action, - &i.ResourceType, - &i.ResourceID, - &i.Details, - ) - return i, err -} - -const createControl = `-- name: CreateControl :one -INSERT INTO controls (policy_id, name, description) -VALUES ($1, $2, $3) -RETURNING id, policy_id, name, description, created_at, updated_at -` - -type CreateControlParams struct { - PolicyID pgtype.UUID `json:"policy_id"` - Name string `json:"name"` - Description pgtype.Text `json:"description"` -} - -func (q *Queries) CreateControl(ctx context.Context, arg CreateControlParams) (Control, error) { - row := q.db.QueryRow(ctx, createControl, arg.PolicyID, arg.Name, arg.Description) - var i Control - err := row.Scan( - &i.ID, - &i.PolicyID, - &i.Name, - &i.Description, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const createFinding = `-- name: CreateFinding :one -INSERT INTO findings (control_id, resource_id, status, details) -VALUES ($1, $2, $3, $4) -RETURNING id, control_id, resource_id, status, details, created_at, updated_at -` - -type CreateFindingParams struct { - ControlID pgtype.UUID `json:"control_id"` - ResourceID string `json:"resource_id"` - Status FindingStatus `json:"status"` - Details []byte `json:"details"` -} - -func (q *Queries) CreateFinding(ctx context.Context, arg CreateFindingParams) (Finding, error) { - row := q.db.QueryRow(ctx, createFinding, - arg.ControlID, - arg.ResourceID, - arg.Status, - arg.Details, - ) - var i Finding - err := row.Scan( - &i.ID, - &i.ControlID, - &i.ResourceID, - &i.Status, - &i.Details, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const createPolicy = `-- name: CreatePolicy :one -INSERT INTO policies (name, description) -VALUES ($1, $2) -RETURNING id, name, description, created_at, updated_at -` - -type CreatePolicyParams struct { - Name string `json:"name"` - Description pgtype.Text `json:"description"` -} - -func (q *Queries) CreatePolicy(ctx context.Context, arg CreatePolicyParams) (Policy, error) { - row := q.db.QueryRow(ctx, createPolicy, arg.Name, arg.Description) - var i Policy - err := row.Scan( - &i.ID, - &i.Name, - &i.Description, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const createSecurityProfile = `-- name: CreateSecurityProfile :one -INSERT INTO security_profiles (name, risk_level) -VALUES ($1, $2) -RETURNING id, name, risk_level, created_at, updated_at -` - -type CreateSecurityProfileParams struct { - Name string `json:"name"` - RiskLevel RiskLevel `json:"risk_level"` -} - -func (q *Queries) CreateSecurityProfile(ctx context.Context, arg CreateSecurityProfileParams) (SecurityProfile, error) { - row := q.db.QueryRow(ctx, createSecurityProfile, arg.Name, arg.RiskLevel) - var i SecurityProfile - err := row.Scan( - &i.ID, - &i.Name, - &i.RiskLevel, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - -const listAuditLogs = `-- name: ListAuditLogs :many -SELECT id, timestamp, actor_id, action, resource_type, resource_id, details FROM audit_logs -ORDER BY timestamp DESC -LIMIT $1 OFFSET $2 -` - -type ListAuditLogsParams struct { - Limit int32 `json:"limit"` - Offset int32 `json:"offset"` -} - -func (q *Queries) ListAuditLogs(ctx context.Context, arg ListAuditLogsParams) ([]AuditLog, error) { - rows, err := q.db.Query(ctx, listAuditLogs, arg.Limit, arg.Offset) - if err != nil { - return nil, err - } - defer rows.Close() - var items []AuditLog - for rows.Next() { - var i AuditLog - if err := rows.Scan( - &i.ID, - &i.Timestamp, - &i.ActorID, - &i.Action, - &i.ResourceType, - &i.ResourceID, - &i.Details, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const listFindings = `-- name: ListFindings :many -SELECT id, control_id, resource_id, status, details, created_at, updated_at FROM findings -WHERE status = $1 -` - -func (q *Queries) ListFindings(ctx context.Context, status FindingStatus) ([]Finding, error) { - rows, err := q.db.Query(ctx, listFindings, status) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Finding - for rows.Next() { - var i Finding - if err := rows.Scan( - &i.ID, - &i.ControlID, - &i.ResourceID, - &i.Status, - &i.Details, - &i.CreatedAt, - &i.UpdatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} diff --git a/security-governance-core/migrations/000001_init_schema.up.sql b/security-governance-core/migrations/000001_init_schema.up.sql deleted file mode 100644 index ecf69a1..0000000 --- a/security-governance-core/migrations/000001_init_schema.up.sql +++ /dev/null @@ -1,78 +0,0 @@ --- +migrate Up -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - -CREATE TYPE finding_status AS ENUM ('open', 'resolved', 'ignored'); -CREATE TYPE risk_level AS ENUM ('low', 'medium', 'high', 'critical'); - -CREATE TABLE security_profiles ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - name VARCHAR(255) NOT NULL UNIQUE, - risk_level risk_level NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE TABLE policies ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - name VARCHAR(255) NOT NULL UNIQUE, - description TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE TABLE controls ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - policy_id UUID NOT NULL REFERENCES policies(id) ON DELETE CASCADE, - name VARCHAR(255) NOT NULL, - description TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE TABLE findings ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - control_id UUID NOT NULL REFERENCES controls(id) ON DELETE CASCADE, - resource_id VARCHAR(255) NOT NULL, - status finding_status NOT NULL, - details JSONB, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE TABLE audit_logs ( - id BIGSERIAL PRIMARY KEY, - timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), - actor_id VARCHAR(255) NOT NULL, - action VARCHAR(255) NOT NULL, - resource_type VARCHAR(255), - resource_id VARCHAR(255), - details JSONB -); - --- Make audit_logs append-only -CREATE FUNCTION make_audit_log_immutable() RETURNS TRIGGER AS $$ -BEGIN - RAISE EXCEPTION 'Audit log records cannot be modified or deleted'; -END; -$$ LANGUAGE plpgsql; - -CREATE TRIGGER audit_log_immutable_trigger -BEFORE UPDATE OR DELETE ON audit_logs -FOR EACH ROW EXECUTE PROCEDURE make_audit_log_immutable(); - --- Indexes -CREATE INDEX ON controls (policy_id); -CREATE INDEX ON findings (control_id); -CREATE INDEX ON audit_logs (actor_id, timestamp DESC); -CREATE INDEX ON audit_logs (resource_type, resource_id); - --- +migrate Down -DROP TRIGGER IF EXISTS audit_log_immutable_trigger ON audit_logs; -DROP FUNCTION IF EXISTS make_audit_log_immutable(); -DROP TABLE IF EXISTS audit_logs; -DROP TABLE IF EXISTS findings; -DROP TABLE IF EXISTS controls; -DROP TABLE IF EXISTS policies; -DROP TABLE IF EXISTS security_profiles; -DROP TYPE IF EXISTS finding_status; -DROP TYPE IF EXISTS risk_level; diff --git a/security-governance-core/sqlc.yaml b/security-governance-core/sqlc.yaml deleted file mode 100644 index d719475..0000000 --- a/security-governance-core/sqlc.yaml +++ /dev/null @@ -1,11 +0,0 @@ -version: "2" -sql: - - engine: "postgresql" - queries: "db/query.sql" - schema: "migrations" - gen: - go: - package: "db" - out: "internal/db" - sql_package: "pgx/v5" - emit_json_tags: true