From 727f28886152100fdf81873a26df461165a08c2a Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Thu, 20 Aug 2026 22:43:17 +0100 Subject: [PATCH 01/64] feat: add health vertical tracer --- .github/workflows/ci.yml | 43 +- README.md | 13 +- backend/.dockerignore | 8 + backend/Dockerfile | 20 + backend/cmd/api/main.go | 66 +++ backend/go.mod | 3 + backend/internal/health/service.go | 44 ++ backend/internal/health/service_test.go | 33 ++ backend/internal/httpapi/health_handler.go | 73 +++ .../internal/httpapi/health_handler_test.go | 77 +++ compose.yaml | 29 + context/architecture.md | 12 +- context/stack.md | 6 +- frontend/.dockerignore | 8 + frontend/.env.example | 2 + frontend/.gitignore | 1 + frontend/.prettierignore | 1 + frontend/Dockerfile | 23 + frontend/README.md | 22 +- frontend/eslint.config.mjs | 1 + frontend/messages/en.json | 5 + frontend/messages/es.json | 5 + frontend/messages/pt-PT.json | 5 + frontend/openapi-ts.config.ts | 7 + frontend/package-lock.json | 535 ++++++++++++++++++ frontend/package.json | 5 +- frontend/scripts/check-codegen.mjs | 72 +++ frontend/src/app/[locale]/page.tsx | 5 + frontend/src/app/api/v1/health/route.test.ts | 128 +++++ frontend/src/app/api/v1/health/route.ts | 89 +++ .../health-status-indicator.test.tsx | 67 +++ .../components/health-status-indicator.tsx | 82 +++ .../landing/components/landing-shell.test.tsx | 5 + .../landing/components/landing-shell.tsx | 6 + .../src/shared/api/generated/client.gen.ts | 16 + .../shared/api/generated/client/client.gen.ts | 280 +++++++++ .../src/shared/api/generated/client/index.ts | 25 + .../shared/api/generated/client/types.gen.ts | 217 +++++++ .../shared/api/generated/client/utils.gen.ts | 318 +++++++++++ .../src/shared/api/generated/core/auth.gen.ts | 41 ++ .../api/generated/core/bodySerializer.gen.ts | 82 +++ .../shared/api/generated/core/params.gen.ts | 169 ++++++ .../api/generated/core/pathSerializer.gen.ts | 171 ++++++ .../generated/core/queryKeySerializer.gen.ts | 117 ++++ .../generated/core/serverSentEvents.gen.ts | 242 ++++++++ .../shared/api/generated/core/types.gen.ts | 104 ++++ .../shared/api/generated/core/utils.gen.ts | 140 +++++ frontend/src/shared/api/generated/index.ts | 4 + frontend/src/shared/api/generated/sdk.gen.ts | 26 + .../src/shared/api/generated/types.gen.ts | 65 +++ frontend/vitest.config.ts | 6 + openapi/juntly-api.v1.yaml | 113 ++++ 52 files changed, 3626 insertions(+), 11 deletions(-) create mode 100644 backend/.dockerignore create mode 100644 backend/Dockerfile create mode 100644 backend/cmd/api/main.go create mode 100644 backend/go.mod create mode 100644 backend/internal/health/service.go create mode 100644 backend/internal/health/service_test.go create mode 100644 backend/internal/httpapi/health_handler.go create mode 100644 backend/internal/httpapi/health_handler_test.go create mode 100644 compose.yaml create mode 100644 frontend/.dockerignore create mode 100644 frontend/.env.example create mode 100644 frontend/Dockerfile create mode 100644 frontend/openapi-ts.config.ts create mode 100644 frontend/scripts/check-codegen.mjs create mode 100644 frontend/src/app/api/v1/health/route.test.ts create mode 100644 frontend/src/app/api/v1/health/route.ts create mode 100644 frontend/src/features/landing/components/health-status-indicator.test.tsx create mode 100644 frontend/src/features/landing/components/health-status-indicator.tsx create mode 100644 frontend/src/shared/api/generated/client.gen.ts create mode 100644 frontend/src/shared/api/generated/client/client.gen.ts create mode 100644 frontend/src/shared/api/generated/client/index.ts create mode 100644 frontend/src/shared/api/generated/client/types.gen.ts create mode 100644 frontend/src/shared/api/generated/client/utils.gen.ts create mode 100644 frontend/src/shared/api/generated/core/auth.gen.ts create mode 100644 frontend/src/shared/api/generated/core/bodySerializer.gen.ts create mode 100644 frontend/src/shared/api/generated/core/params.gen.ts create mode 100644 frontend/src/shared/api/generated/core/pathSerializer.gen.ts create mode 100644 frontend/src/shared/api/generated/core/queryKeySerializer.gen.ts create mode 100644 frontend/src/shared/api/generated/core/serverSentEvents.gen.ts create mode 100644 frontend/src/shared/api/generated/core/types.gen.ts create mode 100644 frontend/src/shared/api/generated/core/utils.gen.ts create mode 100644 frontend/src/shared/api/generated/index.ts create mode 100644 frontend/src/shared/api/generated/sdk.gen.ts create mode 100644 frontend/src/shared/api/generated/types.gen.ts create mode 100644 openapi/juntly-api.v1.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e268b32..5fd5a22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,26 +1,32 @@ -name: Frontend CI +name: Foundation CI on: push: branches: [main, staging, development] paths: - "frontend/**" + - "backend/**" + - "openapi/**" + - "compose.yaml" - ".github/workflows/ci.yml" pull_request: branches: [main, staging, development] paths: - "frontend/**" + - "backend/**" + - "openapi/**" + - "compose.yaml" - ".github/workflows/ci.yml" permissions: contents: read concurrency: - group: frontend-ci-${{ github.workflow }}-${{ github.ref }} + group: foundation-ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - verify: + frontend: name: Frontend verification runs-on: ubuntu-latest timeout-minutes: 15 @@ -43,3 +49,34 @@ jobs: - name: Verify frontend run: npm run verify + + backend: + name: Backend verification + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: backend + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up Go + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 + with: + go-version-file: backend/go.mod + cache: false + + - name: Test backend + run: go test ./... + + topology: + name: Container topology verification + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Validate Compose topology + run: docker compose -f compose.yaml config --quiet diff --git a/README.md b/README.md index b4998cc..a64f998 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,27 @@ This repository is at the foundation stage. The initial delivery contains: - Durable product, architecture, security, UI, workflow, and decision context. - A localized responsive Next.js frontend shell under `frontend/` after the scaffold commit. - pt-PT default, English support, and Spanish-ready routing/messages. -- Frontend test, format, lint, type, build, audit, CI, and runtime-verification foundations. +- Frontend test, format, lint, type, build, dependency-audit, CI, and runtime-verification foundations. +- A versioned OpenAPI health contract, generated TypeScript client, same-origin BFF, and narrow Go health API under `backend/`. +- A local Docker Compose proof for the frontend and API only; no database is included. -It does **not** yet implement accounts, provider profiles, listings, search, chat, quotations, bookings, reviews, payments, Go/OpenAPI, Clerk, Supabase, Redis, object storage, Docker, or production deployment. +It does **not** yet implement accounts, provider profiles, listings, search, +chat, quotations, bookings, reviews, payments, Clerk, Supabase, Redis, object +storage, or production deployment. ## Repository layout ```text juntly/ ├── frontend/ Next.js frontend (created by the scaffold commit) +├── backend/ Go API health-tracer foundation +├── openapi/ versioned API contracts ├── context/ sanitized durable project reference +├── compose.yaml local frontend/API development topology └── AGENTS.md project operating rules ``` -`backend/` and `supabase/` will be created only when the approved API-foundation slice implements them. +`supabase/` will be created only when its approved foundation slice implements it. ## Context diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..7d39931 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,8 @@ +.git +.env +.env.* +!.env.example +*.key +*.pem +*.log +bin/ diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..76c888a --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,20 @@ +FROM golang:1.26.2-alpine AS build + +WORKDIR /src + +COPY go.mod ./ +RUN go mod download + +COPY . ./ +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w -X main.version=0.1.0" -o /out/juntly-api ./cmd/api + +FROM alpine:3.22 + +RUN addgroup -S juntly && adduser -S -G juntly juntly + +COPY --from=build /out/juntly-api /usr/local/bin/juntly-api + +USER juntly +EXPOSE 8080 + +ENTRYPOINT ["/usr/local/bin/juntly-api"] diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go new file mode 100644 index 0000000..f03bcfe --- /dev/null +++ b/backend/cmd/api/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "context" + "errors" + "log/slog" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/health" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/httpapi" +) + +var version = "0.1.0" + +func main() { + if err := run(); err != nil { + slog.Error("api stopped", "error", err) + os.Exit(1) + } +} + +func run() error { + addr := os.Getenv("JUNTLY_API_ADDR") + if addr == "" { + addr = ":8080" + } + + service := health.NewService(version, time.Now) + server := &http.Server{ + Addr: addr, + Handler: httpapi.NewRouter(service), + ReadHeaderTimeout: 5 * time.Second, + } + + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + errs := make(chan error, 1) + go func() { + slog.Info("api listening", "addr", addr) + errs <- server.ListenAndServe() + }() + + select { + case <-ctx.Done(): + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := server.Shutdown(shutdownCtx); err != nil { + return err + } + err := <-errs + if errors.Is(err, http.ErrServerClosed) { + return nil + } + return err + case err := <-errs: + if errors.Is(err, http.ErrServerClosed) { + return nil + } + return err + } +} diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..1639b0d --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,3 @@ +module github.com/SourceSenseiTheRealOne/juntly/backend + +go 1.26 diff --git a/backend/internal/health/service.go b/backend/internal/health/service.go new file mode 100644 index 0000000..4a27f9f --- /dev/null +++ b/backend/internal/health/service.go @@ -0,0 +1,44 @@ +package health + +import "time" + +const ServiceName = "juntly-api" + +type Clock func() time.Time + +type Service struct { + version string + now Clock +} + +type Result struct { + Status string `json:"status"` + Service string `json:"service"` + Version string `json:"version"` + CheckedAt time.Time `json:"checkedAt"` + RequestID string `json:"requestId"` +} + +func NewService(version string, now Clock) Service { + if version == "" { + version = "dev" + } + if now == nil { + now = time.Now + } + + return Service{ + version: version, + now: now, + } +} + +func (s Service) Check(requestID string) Result { + return Result{ + Status: "ok", + Service: ServiceName, + Version: s.version, + CheckedAt: s.now().UTC(), + RequestID: requestID, + } +} diff --git a/backend/internal/health/service_test.go b/backend/internal/health/service_test.go new file mode 100644 index 0000000..d56f8ed --- /dev/null +++ b/backend/internal/health/service_test.go @@ -0,0 +1,33 @@ +package health_test + +import ( + "testing" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/health" +) + +func TestServiceCheckReturnsVersionedHealth(t *testing.T) { + now := time.Date(2026, 8, 20, 9, 30, 0, 0, time.UTC) + service := health.NewService("0.1.0", func() time.Time { + return now + }) + + result := service.Check("req_test_123") + + if result.Status != "ok" { + t.Fatalf("Status = %q, want ok", result.Status) + } + if result.Service != "juntly-api" { + t.Fatalf("Service = %q, want juntly-api", result.Service) + } + if result.Version != "0.1.0" { + t.Fatalf("Version = %q, want 0.1.0", result.Version) + } + if !result.CheckedAt.Equal(now) { + t.Fatalf("CheckedAt = %s, want %s", result.CheckedAt, now) + } + if result.RequestID != "req_test_123" { + t.Fatalf("RequestID = %q, want req_test_123", result.RequestID) + } +} diff --git a/backend/internal/httpapi/health_handler.go b/backend/internal/httpapi/health_handler.go new file mode 100644 index 0000000..cf9844f --- /dev/null +++ b/backend/internal/httpapi/health_handler.go @@ -0,0 +1,73 @@ +package httpapi + +import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "net/http" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/health" +) + +const RequestIDHeader = "X-Request-ID" + +type HealthHandler struct { + service health.Service +} + +func NewHealthHandler(service health.Service) HealthHandler { + return HealthHandler{service: service} +} + +func (h HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + w.Header().Set("Allow", http.MethodGet) + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + + requestID := requestIDFromHeader(r.Header.Get(RequestIDHeader)) + w.Header().Set(RequestIDHeader, requestID) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + _ = json.NewEncoder(w).Encode(h.service.Check(requestID)) +} + +func NewRouter(service health.Service) http.Handler { + mux := http.NewServeMux() + mux.Handle("/api/v1/health", NewHealthHandler(service)) + return mux +} + +func requestIDFromHeader(value string) string { + if validRequestID(value) { + return value + } + + var randomBytes [16]byte + if _, err := rand.Read(randomBytes[:]); err != nil { + return "req_unavailable" + } + + return "req_" + hex.EncodeToString(randomBytes[:]) +} + +func validRequestID(value string) bool { + if len(value) < 8 || len(value) > 128 { + return false + } + + for _, char := range value { + switch { + case char >= 'A' && char <= 'Z': + case char >= 'a' && char <= 'z': + case char >= '0' && char <= '9': + case char == '.', char == '_', char == ':', char == '-': + default: + return false + } + } + + return true +} diff --git a/backend/internal/httpapi/health_handler_test.go b/backend/internal/httpapi/health_handler_test.go new file mode 100644 index 0000000..93e2238 --- /dev/null +++ b/backend/internal/httpapi/health_handler_test.go @@ -0,0 +1,77 @@ +package httpapi_test + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/health" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/httpapi" +) + +func TestHealthHandlerReturnsCorrelationHeaderAndBody(t *testing.T) { + service := health.NewService("0.1.0", func() time.Time { + return time.Date(2026, 8, 20, 9, 30, 0, 0, time.UTC) + }) + handler := httpapi.NewHealthHandler(service) + request := httptest.NewRequest(http.MethodGet, "/api/v1/health", nil) + request.Header.Set("X-Request-ID", "req_from_client") + response := httptest.NewRecorder() + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusOK { + t.Fatalf("status = %d, want %d", response.Code, http.StatusOK) + } + if got := response.Header().Get("X-Request-ID"); got != "req_from_client" { + t.Fatalf("X-Request-ID = %q, want req_from_client", got) + } + if got := response.Header().Get("Content-Type"); got != "application/json" { + t.Fatalf("Content-Type = %q, want application/json", got) + } + + var body struct { + Status string `json:"status"` + Service string `json:"service"` + Version string `json:"version"` + CheckedAt string `json:"checkedAt"` + RequestID string `json:"requestId"` + } + if err := json.NewDecoder(response.Body).Decode(&body); err != nil { + t.Fatalf("decode response: %v", err) + } + if body.RequestID != "req_from_client" { + t.Fatalf("body requestId = %q, want req_from_client", body.RequestID) + } + if body.Status != "ok" || body.Service != "juntly-api" || body.Version != "0.1.0" { + t.Fatalf("unexpected health body: %#v", body) + } +} + +func TestHealthHandlerGeneratesCorrelationIDWhenMissing(t *testing.T) { + service := health.NewService("0.1.0", func() time.Time { + return time.Date(2026, 8, 20, 9, 30, 0, 0, time.UTC) + }) + handler := httpapi.NewHealthHandler(service) + request := httptest.NewRequest(http.MethodGet, "/api/v1/health", nil) + response := httptest.NewRecorder() + + handler.ServeHTTP(response, request) + + headerRequestID := response.Header().Get("X-Request-ID") + if headerRequestID == "" { + t.Fatal("X-Request-ID header is empty") + } + + var body struct { + RequestID string `json:"requestId"` + } + if err := json.NewDecoder(response.Body).Decode(&body); err != nil { + t.Fatalf("decode response: %v", err) + } + if body.RequestID != headerRequestID { + t.Fatalf("body requestId = %q, want header %q", body.RequestID, headerRequestID) + } +} diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..1118cf2 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,29 @@ +services: + api: + build: + context: ./backend + environment: + JUNTLY_API_ADDR: :8080 + healthcheck: + test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8080/api/v1/health || exit 1"] + interval: 5s + timeout: 3s + retries: 10 + start_period: 2s + + frontend: + build: + context: ./frontend + environment: + JUNTLY_API_ORIGIN: http://api:8080 + depends_on: + api: + condition: service_healthy + ports: + - "4200:3000" + healthcheck: + test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:3000/pt-PT || exit 1"] + interval: 5s + timeout: 3s + retries: 10 + start_period: 5s diff --git a/context/architecture.md b/context/architecture.md index 4e4d557..fd9b547 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -10,7 +10,10 @@ juntly/ └── supabase/ project-owned PostgreSQL/PostGIS configuration (later slice) ``` -Only `frontend/` is created during the initial bootstrap. Empty future-runtime directories are prohibited. +The initial bootstrap created only `frontend/`. The health-tracer foundation adds +`backend/` and the versioned OpenAPI contract because both now contain working +code; `supabase/` remains absent until its own implemented slice. Empty +future-runtime directories remain prohibited. ## Approved request path @@ -69,4 +72,9 @@ Local development uses a child-owned Supabase stack for PostgreSQL/PostGIS and m ## Vertical tracer for the next foundation slice -The smallest full-stack proof will cross localized Next.js UI/shared generated client → same-origin BFF → Go `/api/v1/health` transport → framework-independent application service → response mapping. It must pass tests/builds, run in containers, and return matching correlation evidence. The current frontend-only bootstrap does not claim this tracer exists. +The implemented smallest full-stack proof crosses a localized Next.js client +island → same-origin BFF → generated OpenAPI client → Go `/api/v1/health` +transport → framework-independent application service → response mapping. It +returns one matching `X-Request-ID`/body correlation value, fails closed with a +privacy-safe BFF error, and runs locally in frontend/API containers. It does +not claim any Clerk, persistence, marketplace, or payment flow exists. diff --git a/context/stack.md b/context/stack.md index 8f06f40..bf4e9c8 100644 --- a/context/stack.md +++ b/context/stack.md @@ -33,4 +33,8 @@ ## Current implementation boundary -The bootstrap implements context and the Next.js frontend shell only. Go, OpenAPI generation, BFF proxying, Clerk, Supabase, Redis, object storage, background workers, payments, and Docker runtime topology are not yet implemented and must not be represented as working. +The foundation now implements the localized Next.js shell, a generated OpenAPI +TypeScript client, same-origin health BFF, and a narrow Go health API with a +two-container Docker proof. Clerk, Supabase, Redis, object storage, background +workers, marketplace flows, payments, and production deployment are not yet +implemented and must not be represented as working. diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..bc0010b --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,8 @@ +.git +.next +node_modules +coverage +.env +.env.* +!.env.example +*.log diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..c6a55a8 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,2 @@ +# Server-side origin used only by the Next.js BFF. Do not use a NEXT_PUBLIC_ prefix. +JUNTLY_API_ORIGIN=http://127.0.0.1:8080 diff --git a/frontend/.gitignore b/frontend/.gitignore index 5ef6a52..7b8da95 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -32,6 +32,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +!.env.example # vercel .vercel diff --git a/frontend/.prettierignore b/frontend/.prettierignore index b322dee..e8652f7 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -8,3 +8,4 @@ next-env.d.ts package-lock.json AGENTS.md CLAUDE.md +src/shared/api/generated/ diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..c518cbb --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,23 @@ +FROM node:24.13.1-alpine AS build + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . ./ +RUN npm run build + +FROM node:24.13.1-alpine AS runtime + +ENV NODE_ENV=production +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev --ignore-scripts +COPY --from=build /app/.next ./.next + +USER node +EXPOSE 3000 + +CMD ["npm", "run", "start", "--", "--hostname", "0.0.0.0", "--port", "3000"] diff --git a/frontend/README.md b/frontend/README.md index 3d6aa5e..f70158f 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -9,9 +9,13 @@ This bootstrap intentionally provides only: - pt-PT default routing, English support, and Spanish-ready translations. - Localized metadata and route boundaries. - A responsive, accessible product-introduction shell. +- A same-origin `/api/v1/health` BFF route backed by the generated OpenAPI client. +- A localized client-side health indicator. - Test, format, lint, type, build, dependency-audit, and CI foundations. -Authentication, listings, search, chat, quotations, bookings, payments, the Go API, and external infrastructure are not implemented yet. +Authentication, listings, search, chat, quotations, bookings, payments, and +external infrastructure are not implemented yet. The only Go endpoint is the +foundation health tracer. ## Requirements @@ -27,6 +31,12 @@ npm test npm run verify ``` +The OpenAPI artifact is checked with: + +```bash +npm run codegen:check +``` + For a production runtime probe: ```bash @@ -36,4 +46,14 @@ npm run start -- --hostname 127.0.0.1 --port 4200 Then open `http://127.0.0.1:4200/`; locale routing redirects to pt-PT by default. +For local frontend/API proof, run from the repository root: + +```bash +docker compose up --build +``` + +The compose topology supplies the server-only BFF origin. For a native frontend +runtime, copy `.env.example` to an ignored `.env.local` and start the Go API on +`127.0.0.1:8080`. + Project-wide architecture and product rules live in `../context/`. diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs index 05e726d..3cddb2e 100644 --- a/frontend/eslint.config.mjs +++ b/frontend/eslint.config.mjs @@ -12,6 +12,7 @@ const eslintConfig = defineConfig([ "out/**", "build/**", "next-env.d.ts", + "src/shared/api/generated/**", ]), ]); diff --git a/frontend/messages/en.json b/frontend/messages/en.json index d8d0d9f..e261c52 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -9,6 +9,11 @@ "heading": "Local services, closer to home.", "description": "A simple way to find people with real skills in your community — starting in rural Portugal.", "statusLabel": "The platform is taking shape.", + "healthStatus": { + "checking": "Checking the API.", + "available": "API available.", + "unavailable": "API temporarily unavailable." + }, "visionLinkLabel": "Discover the vision", "visionTitle": "Built for genuine local connections", "visionDescription": "Discovery, contact and trust without taking choice away from people. Juntly connects those who need help with those who know how.", diff --git a/frontend/messages/es.json b/frontend/messages/es.json index e95883e..a01758c 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -9,6 +9,11 @@ "heading": "Servicios locales, más cerca de ti.", "description": "Una forma sencilla de encontrar personas con habilidades reales en tu comunidad — empezando en el Portugal rural.", "statusLabel": "La plataforma está tomando forma.", + "healthStatus": { + "checking": "Comprobando la API.", + "available": "API disponible.", + "unavailable": "API temporalmente no disponible." + }, "visionLinkLabel": "Descubrir la visión", "visionTitle": "Creada para conexiones locales reales", "visionDescription": "Descubrimiento, contacto y confianza sin quitar elección a las personas. Juntly conecta a quien necesita ayuda con quien sabe hacerla.", diff --git a/frontend/messages/pt-PT.json b/frontend/messages/pt-PT.json index e5682b6..a241eb0 100644 --- a/frontend/messages/pt-PT.json +++ b/frontend/messages/pt-PT.json @@ -9,6 +9,11 @@ "heading": "Serviços locais, mais perto de si.", "description": "Uma forma simples de encontrar pessoas com competências reais na sua comunidade — começando no Portugal rural.", "statusLabel": "A plataforma está a nascer.", + "healthStatus": { + "checking": "A verificar a API.", + "available": "API disponível.", + "unavailable": "API temporariamente indisponível." + }, "visionLinkLabel": "Conhecer a visão", "visionTitle": "Criada para ligações locais reais", "visionDescription": "Descoberta, contacto e confiança sem retirar a escolha às pessoas. A Juntly liga quem precisa a quem sabe fazer.", diff --git a/frontend/openapi-ts.config.ts b/frontend/openapi-ts.config.ts new file mode 100644 index 0000000..38bcdd1 --- /dev/null +++ b/frontend/openapi-ts.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "@hey-api/openapi-ts"; + +export default defineConfig({ + input: "../openapi/juntly-api.v1.yaml", + output: "src/shared/api/generated", + plugins: ["@hey-api/typescript", "@hey-api/sdk", "@hey-api/client-fetch"], +}); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a1a6baa..cacfb43 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,6 +14,7 @@ "react-dom": "19.2.8" }, "devDependencies": { + "@hey-api/openapi-ts": "0.97.0", "@tailwindcss/postcss": "^4", "@testing-library/jest-dom": "7.0.1", "@testing-library/react": "16.3.2", @@ -730,6 +731,143 @@ "@formatjs/fast-memoize": "3.1.7" } }, + "node_modules/@hey-api/codegen-core": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@hey-api/codegen-core/-/codegen-core-0.8.1.tgz", + "integrity": "sha512-Iciv2vUCJTW9lWM/ROvyZLblmcbYJHPuXfzb1SzeDVVn4xEXu2ilLU1pq3fn+09FZ/Y0P7VyvRE47UDU6om8xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hey-api/types": "0.1.4", + "ansi-colors": "4.1.3", + "c12": "3.3.4", + "color-support": "1.1.3" + }, + "engines": { + "node": ">=22.13.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/json-schema-ref-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.4.1.tgz", + "integrity": "sha512-DoPJGxVApDlktP1yYLjmOrF0YBEqb32ieCbx1S1i09n8TyCgdoh4yQaQ3kp0sMTauH+bwNKPsFh7S8qiWCoKZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "7.1.3", + "@types/json-schema": "7.0.15", + "yaml": "2.8.3" + }, + "engines": { + "node": ">=22.13.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/openapi-ts": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.97.0.tgz", + "integrity": "sha512-WZkKgrDlZpxKlDv2HkBCzaAYeuM+EtZKFmKGBv9/JblAKpX3JQTROi7PzlCZE3eisetRPSakbcRgn+LGyB7EiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hey-api/codegen-core": "0.8.1", + "@hey-api/json-schema-ref-parser": "1.4.1", + "@hey-api/shared": "0.4.2", + "@hey-api/spec-types": "0.2.0", + "@hey-api/types": "0.1.4", + "@lukeed/ms": "2.0.2", + "ansi-colors": "4.1.3", + "color-support": "1.1.3", + "commander": "14.0.3", + "get-tsconfig": "4.14.0" + }, + "bin": { + "openapi-ts": "bin/run.js" + }, + "engines": { + "node": ">=22.13.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "typescript": ">=5.5.3 || >=6.0.0 || 6.0.1-rc" + } + }, + "node_modules/@hey-api/openapi-ts/node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/@hey-api/shared": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@hey-api/shared/-/shared-0.4.2.tgz", + "integrity": "sha512-4fconS10E0Xr4/acV8G+BkApxaIStxrT0GhB9BDTQWvrFTy5/nV933SyFk8qImcbpKvgv9hpn3N+7bV8oFrbjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hey-api/codegen-core": "0.8.1", + "@hey-api/json-schema-ref-parser": "1.4.1", + "@hey-api/spec-types": "0.2.0", + "@hey-api/types": "0.1.4", + "ansi-colors": "4.1.3", + "cross-spawn": "7.0.6", + "open": "11.0.0", + "semver": "7.7.4" + }, + "engines": { + "node": ">=22.13.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/shared/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@hey-api/spec-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@hey-api/spec-types/-/spec-types-0.2.0.tgz", + "integrity": "sha512-ibQ8Is7evMavzr8GNyJCcTg975d8DpaMUyLmOrQ85UBdy1l6t1KuRAwgChAbesJsIlNV6gjmlXruWyegDX18Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hey-api/types": "0.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/types": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@hey-api/types/-/types-0.1.4.tgz", + "integrity": "sha512-thWfawrDIP7wSI9ioT13I5soaaqB5vAPIiZmgD8PbeEVKNrkonc0N/Sjj97ezl7oQgusZmaNphGdMKipPO6IBg==", + "dev": true, + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -1347,6 +1485,23 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", @@ -3432,6 +3587,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "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-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3786,6 +3951,51 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c12": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -3893,6 +4103,22 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -3919,6 +4145,26 @@ "dev": true, "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3926,6 +4172,13 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "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", @@ -4083,6 +4336,36 @@ "dev": true, "license": "MIT" }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "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", @@ -4101,6 +4384,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -4119,6 +4415,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -4130,6 +4433,13 @@ "node": ">=6" } }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "dev": true, + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -4160,6 +4470,19 @@ "license": "MIT", "peer": true }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "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", @@ -5127,6 +5450,13 @@ "node": ">=12.0.0" } }, + "node_modules/exsolve": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", + "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", + "dev": true, + "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", @@ -5417,6 +5747,16 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/giget": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz", + "integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==", + "dev": true, + "license": "MIT", + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -5849,6 +6189,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-document.all": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", @@ -5922,6 +6278,38 @@ "node": ">=0.10.0" } }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -6127,6 +6515,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -7106,6 +7510,34 @@ "node": ">=12.20.0" } }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "dev": true, + "license": "MIT" + }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "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", @@ -7235,6 +7667,13 @@ "dev": true, "license": "MIT" }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7254,6 +7693,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, "node_modules/po-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.2.0.tgz", @@ -7299,6 +7750,19 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7485,6 +7949,17 @@ ], "license": "MIT" }, + "node_modules/rc9": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", + "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.6", + "destr": "^2.0.5" + } + }, "node_modules/react": { "version": "19.2.8", "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", @@ -7513,6 +7988,20 @@ "dev": true, "license": "MIT" }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -7669,6 +8158,19 @@ "@rolldown/binding-win32-x64-msvc": "1.2.4" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -9321,6 +9823,23 @@ "node": ">=0.10.0" } }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -9345,6 +9864,22 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "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", diff --git a/frontend/package.json b/frontend/package.json index 9d45f49..e0f7a52 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,6 +4,8 @@ "private": true, "type": "module", "scripts": { + "codegen": "openapi-ts", + "codegen:check": "node scripts/check-codegen.mjs", "dev": "next dev", "build": "next build", "start": "next start", @@ -13,7 +15,7 @@ "test:watch": "vitest", "lint": "eslint . --max-warnings=0", "typecheck": "next typegen && tsc --noEmit", - "verify": "npm run format:check && npm test && npm run lint && npm run typecheck && npm run build && npm audit --audit-level=high" + "verify": "npm run codegen:check && npm run format:check && npm test && npm run lint && npm run typecheck && npm run build && npm audit --audit-level=high" }, "dependencies": { "next": "16.3.1", @@ -22,6 +24,7 @@ "react-dom": "19.2.8" }, "devDependencies": { + "@hey-api/openapi-ts": "0.97.0", "@tailwindcss/postcss": "^4", "@testing-library/jest-dom": "7.0.1", "@testing-library/react": "16.3.2", diff --git a/frontend/scripts/check-codegen.mjs b/frontend/scripts/check-codegen.mjs new file mode 100644 index 0000000..107516c --- /dev/null +++ b/frontend/scripts/check-codegen.mjs @@ -0,0 +1,72 @@ +import { createHash } from "node:crypto"; +import { readdirSync, readFileSync, statSync } from "node:fs"; +import { relative, resolve } from "node:path"; +import { spawnSync } from "node:child_process"; + +const generatedDir = resolve("src/shared/api/generated"); + +function listFiles(dir) { + try { + return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + const fullPath = resolve(dir, entry.name); + + if (entry.isDirectory()) { + return listFiles(fullPath); + } + + if (entry.isFile()) { + return [fullPath]; + } + + return []; + }); + } catch (error) { + if (error?.code === "ENOENT") { + return []; + } + + throw error; + } +} + +function hashGeneratedTree() { + const hash = createHash("sha256"); + const files = listFiles(generatedDir).sort(); + + for (const file of files) { + const stats = statSync(file); + hash.update(relative(generatedDir, file)); + hash.update(String(stats.size)); + hash.update(readFileSync(file)); + } + + return hash.digest("hex"); +} + +const before = hashGeneratedTree(); +const result = + process.platform === "win32" + ? spawnSync("cmd.exe", ["/d", "/s", "/c", "npm.cmd run codegen"], { + cwd: process.cwd(), + stdio: "inherit", + }) + : spawnSync("npm", ["run", "codegen"], { + cwd: process.cwd(), + stdio: "inherit", + }); + +if (result.error) { + process.stderr.write(`${result.error.message}\n`); + process.exit(1); +} + +if (result.status !== 0) { + process.exit(result.status ?? 1); +} + +const after = hashGeneratedTree(); + +if (before !== after) { + process.stderr.write("Generated OpenAPI client is out of date.\n"); + process.exit(1); +} diff --git a/frontend/src/app/[locale]/page.tsx b/frontend/src/app/[locale]/page.tsx index 99934e7..80ece8b 100644 --- a/frontend/src/app/[locale]/page.tsx +++ b/frontend/src/app/[locale]/page.tsx @@ -12,6 +12,11 @@ export default async function HomePage() { heading={t("heading")} description={t("description")} statusLabel={t("statusLabel")} + healthStatusLabels={{ + checking: t("healthStatus.checking"), + available: t("healthStatus.available"), + unavailable: t("healthStatus.unavailable"), + }} visionLinkLabel={t("visionLinkLabel")} visionTitle={t("visionTitle")} visionDescription={t("visionDescription")} diff --git a/frontend/src/app/api/v1/health/route.test.ts b/frontend/src/app/api/v1/health/route.test.ts new file mode 100644 index 0000000..8cfb20a --- /dev/null +++ b/frontend/src/app/api/v1/health/route.test.ts @@ -0,0 +1,128 @@ +import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; + +import { GET } from "./route"; + +describe("GET /api/v1/health BFF route", () => { + beforeEach(() => { + vi.stubEnv("JUNTLY_API_ORIGIN", "http://go-api:8080"); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + vi.restoreAllMocks(); + }); + + it("forwards the correlation ID upstream and returns matching evidence", async () => { + const upstreamFetch = vi.fn(async (request: Request) => { + expect(request.url).toBe("http://go-api:8080/api/v1/health"); + expect(request.headers.get("X-Request-ID")).toBe("req_browser_123"); + + return Response.json( + { + status: "ok", + service: "juntly-api", + version: "0.1.0", + checkedAt: "2026-08-20T09:30:00Z", + requestId: "req_browser_123", + }, + { + headers: { + "X-Request-ID": "req_browser_123", + }, + }, + ); + }); + vi.stubGlobal("fetch", upstreamFetch); + + const response = await GET( + new Request("http://localhost/api/v1/health", { + headers: { + "X-Request-ID": "req_browser_123", + }, + }), + ); + + expect(response.status).toBe(200); + expect(response.headers.get("X-Request-ID")).toBe("req_browser_123"); + await expect(response.json()).resolves.toEqual({ + status: "ok", + service: "juntly-api", + version: "0.1.0", + checkedAt: "2026-08-20T09:30:00Z", + requestId: "req_browser_123", + }); + expect(upstreamFetch).toHaveBeenCalledOnce(); + }); + + it("maps unavailable upstream errors to a privacy-safe 503", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => { + throw new Error( + "connect ECONNREFUSED http://go-api:8080/api/v1/health", + ); + }), + ); + + const response = await GET( + new Request("http://localhost/api/v1/health", { + headers: { + "X-Request-ID": "req_browser_503", + }, + }), + ); + + expect(response.status).toBe(503); + expect(response.headers.get("X-Request-ID")).toBe("req_browser_503"); + const body = await response.json(); + expect(body).toEqual({ + error: { + code: "SERVICE_UNAVAILABLE", + message: "Service unavailable", + requestId: "req_browser_503", + }, + }); + expect(JSON.stringify(body)).not.toContain("go-api"); + expect(JSON.stringify(body)).not.toContain("ECONNREFUSED"); + }); + + it("maps malformed upstream success to the same stable 503", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => + Response.json( + { + status: "ok", + service: "juntly-api", + version: "0.1.0", + checkedAt: "2026-08-20T09:30:00Z", + requestId: "different_request", + }, + { + headers: { + "X-Request-ID": "different_request", + }, + }, + ), + ), + ); + + const response = await GET( + new Request("http://localhost/api/v1/health", { + headers: { + "X-Request-ID": "req_browser_malformed", + }, + }), + ); + + expect(response.status).toBe(503); + expect(response.headers.get("X-Request-ID")).toBe("req_browser_malformed"); + await expect(response.json()).resolves.toEqual({ + error: { + code: "SERVICE_UNAVAILABLE", + message: "Service unavailable", + requestId: "req_browser_malformed", + }, + }); + }); +}); diff --git a/frontend/src/app/api/v1/health/route.ts b/frontend/src/app/api/v1/health/route.ts new file mode 100644 index 0000000..37bbf30 --- /dev/null +++ b/frontend/src/app/api/v1/health/route.ts @@ -0,0 +1,89 @@ +import { getHealth } from "@/shared/api/generated"; +import type { ErrorResponse, HealthResponse } from "@/shared/api/generated"; + +const requestIDHeader = "X-Request-ID"; +const unavailableMessage = "Service unavailable"; + +export const runtime = "nodejs"; + +export async function GET(request: Request): Promise { + const requestID = readRequestID(request.headers); + const apiOrigin = process.env.JUNTLY_API_ORIGIN; + + if (!apiOrigin) { + return unavailableResponse(requestID); + } + + try { + const upstream = await getHealth({ + baseUrl: apiOrigin, + headers: { + [requestIDHeader]: requestID, + }, + }); + + if ( + upstream.error || + !upstream.response?.ok || + !isHealthResponse(upstream.data, requestID) || + upstream.response.headers.get(requestIDHeader) !== requestID + ) { + return unavailableResponse(requestID); + } + + return Response.json(upstream.data, { + status: 200, + headers: { + [requestIDHeader]: requestID, + }, + }); + } catch { + return unavailableResponse(requestID); + } +} + +function unavailableResponse(requestID: string): Response { + const body: ErrorResponse = { + error: { + code: "SERVICE_UNAVAILABLE", + message: unavailableMessage, + requestId: requestID, + }, + }; + + return Response.json(body, { + status: 503, + headers: { + [requestIDHeader]: requestID, + }, + }); +} + +function readRequestID(headers: Headers): string { + const requestID = headers.get(requestIDHeader); + + if (requestID && isValidRequestID(requestID)) { + return requestID; + } + + return `req_${globalThis.crypto.randomUUID()}`; +} + +function isValidRequestID(value: string): boolean { + return /^[A-Za-z0-9._:-]{8,128}$/.test(value); +} + +function isHealthResponse( + value: HealthResponse | undefined, + requestID: string, +): value is HealthResponse { + return ( + value?.status === "ok" && + value.service === "juntly-api" && + typeof value.version === "string" && + value.version.length > 0 && + typeof value.checkedAt === "string" && + !Number.isNaN(Date.parse(value.checkedAt)) && + value.requestId === requestID + ); +} diff --git a/frontend/src/features/landing/components/health-status-indicator.test.tsx b/frontend/src/features/landing/components/health-status-indicator.test.tsx new file mode 100644 index 0000000..6253398 --- /dev/null +++ b/frontend/src/features/landing/components/health-status-indicator.test.tsx @@ -0,0 +1,67 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { HealthStatusIndicator } from "./health-status-indicator"; + +const labels = { + checking: "A verificar a API.", + available: "API disponível.", + unavailable: "API temporariamente indisponível.", +}; + +describe("HealthStatusIndicator", () => { + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it("calls the same-origin BFF and renders the available state", async () => { + const fetchHealth = vi.fn(async () => + Response.json( + { + status: "ok", + service: "juntly-api", + version: "0.1.0", + checkedAt: "2026-08-20T09:30:00Z", + requestId: "req_ui_123", + }, + { + headers: { + "X-Request-ID": "req_ui_123", + }, + }, + ), + ); + vi.stubGlobal("fetch", fetchHealth); + + render(); + + expect(screen.getByRole("status")).toHaveTextContent(labels.checking); + await waitFor(() => + expect(screen.getByRole("status")).toHaveTextContent(labels.available), + ); + expect(fetchHealth).toHaveBeenCalledWith("/api/v1/health", { + cache: "no-store", + headers: { + Accept: "application/json", + }, + }); + }); + + it("renders the unavailable state without exposing transport details", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => { + throw new Error("connect ECONNREFUSED http://go-api:8080"); + }), + ); + + render(); + + await waitFor(() => + expect(screen.getByRole("status")).toHaveTextContent(labels.unavailable), + ); + expect(screen.getByRole("status")).not.toHaveTextContent("go-api"); + expect(screen.getByRole("status")).not.toHaveTextContent("ECONNREFUSED"); + }); +}); diff --git a/frontend/src/features/landing/components/health-status-indicator.tsx b/frontend/src/features/landing/components/health-status-indicator.tsx new file mode 100644 index 0000000..95529da --- /dev/null +++ b/frontend/src/features/landing/components/health-status-indicator.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { useEffect, useState } from "react"; + +export type HealthStatusLabels = { + checking: string; + available: string; + unavailable: string; +}; + +type HealthStatusIndicatorProps = { + labels: HealthStatusLabels; +}; + +type HealthState = "checking" | "available" | "unavailable"; + +export function HealthStatusIndicator({ labels }: HealthStatusIndicatorProps) { + const [state, setState] = useState("checking"); + + useEffect(() => { + let ignore = false; + + async function checkHealth() { + try { + const response = await fetch("/api/v1/health", { + cache: "no-store", + headers: { + Accept: "application/json", + }, + }); + const body: unknown = await response.json(); + + if (!ignore && response.ok && isAvailableHealth(body)) { + setState("available"); + return; + } + } catch { + // Keep transport details private; show only localized generic state. + } + + if (!ignore) { + setState("unavailable"); + } + } + + void checkHealth(); + + return () => { + ignore = true; + }; + }, []); + + return ( + + + ); +} + +function isAvailableHealth(value: unknown): boolean { + if (!value || typeof value !== "object") { + return false; + } + + const health = value as Record; + + return health.status === "ok" && health.service === "juntly-api"; +} diff --git a/frontend/src/features/landing/components/landing-shell.test.tsx b/frontend/src/features/landing/components/landing-shell.test.tsx index e4db5bd..0316215 100644 --- a/frontend/src/features/landing/components/landing-shell.test.tsx +++ b/frontend/src/features/landing/components/landing-shell.test.tsx @@ -9,6 +9,11 @@ const copy = { heading: "Serviços locais, mais perto de si.", description: "Uma forma simples de encontrar pessoas com competências reais.", statusLabel: "A plataforma está a nascer.", + healthStatusLabels: { + checking: "A verificar a API.", + available: "API disponível.", + unavailable: "API temporariamente indisponível.", + }, visionLinkLabel: "Conhecer a visão", visionTitle: "Criada para ligações locais reais", visionDescription: diff --git a/frontend/src/features/landing/components/landing-shell.tsx b/frontend/src/features/landing/components/landing-shell.tsx index 00b7b06..16d3df7 100644 --- a/frontend/src/features/landing/components/landing-shell.tsx +++ b/frontend/src/features/landing/components/landing-shell.tsx @@ -1,9 +1,13 @@ +import type { HealthStatusLabels } from "./health-status-indicator"; +import { HealthStatusIndicator } from "./health-status-indicator"; + type LandingShellProps = { eyebrow: string; tagline: string; heading: string; description: string; statusLabel: string; + healthStatusLabels: HealthStatusLabels; visionLinkLabel: string; visionTitle: string; visionDescription: string; @@ -16,6 +20,7 @@ export function LandingShell({ heading, description, statusLabel, + healthStatusLabels, visionLinkLabel, visionTitle, visionDescription, @@ -73,6 +78,7 @@ export function LandingShell({ /> {statusLabel} + diff --git a/frontend/src/shared/api/generated/client.gen.ts b/frontend/src/shared/api/generated/client.gen.ts new file mode 100644 index 0000000..42a02c6 --- /dev/null +++ b/frontend/src/shared/api/generated/client.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ baseUrl: 'http://localhost:8080' })); diff --git a/frontend/src/shared/api/generated/client/client.gen.ts b/frontend/src/shared/api/generated/client/client.gen.ts new file mode 100644 index 0000000..377b6c9 --- /dev/null +++ b/frontend/src/shared/api/generated/client/client.gen.ts @@ -0,0 +1,280 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { Client, Config, RequestOptions, ResolvedRequestOptions } from './types.gen'; +import { + buildUrl, + createConfig, + createInterceptors, + getParseAs, + mergeConfigs, + mergeHeaders, + setAuthParams, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: any; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors(); + + const beforeRequest = async < + TData = unknown, + TResponseStyle extends 'data' | 'fields' = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, + >( + options: RequestOptions, + ) => { + const opts = { + ..._config, + ...options, + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined as string | undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body) as string | undefined; + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + const resolvedOpts = opts as typeof opts & + ResolvedRequestOptions; + const url = buildUrl(resolvedOpts); + + return { opts: resolvedOpts, url }; + }; + + const request: Client['request'] = async (options) => { + const throwOnError = options.throwOnError ?? _config.throwOnError; + const responseStyle = options.responseStyle ?? _config.responseStyle; + + let request: Request | undefined; + let response: Response | undefined; + + try { + const { opts, url } = await beforeRequest(options); + const requestInit: ReqInit = { + redirect: 'follow', + ...opts, + body: getValidRequestBody(opts), + }; + + request = new Request(url, requestInit); + + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = opts.fetch!; + + response = await _fetch(request); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { + request, + response, + }; + + if (response.ok) { + const parseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + if (response.status === 204 || response.headers.get('Content-Length') === '0') { + let emptyData: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + emptyData = await response[parseAs](); + break; + case 'formData': + emptyData = new FormData(); + break; + case 'stream': + emptyData = response.body; + break; + case 'json': + default: + emptyData = {}; + break; + } + return opts.responseStyle === 'data' + ? emptyData + : { + data: emptyData, + ...result, + }; + } + + let data: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'text': + data = await response[parseAs](); + break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; read as text and parse if non-empty. + const text = await response.text(); + data = text ? JSON.parse(text) : {}; + break; + } + case 'stream': + return opts.responseStyle === 'data' + ? response.body + : { + data: response.body, + ...result, + }; + } + + if (parseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return opts.responseStyle === 'data' + ? data + : { + data, + ...result, + }; + } + + const textError = await response.text(); + let jsonError: unknown; + + try { + jsonError = JSON.parse(textError); + } catch { + // noop + } + + throw jsonError ?? textError; + } catch (error) { + let finalError = error; + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, options as ResolvedRequestOptions); + } + } + + finalError = finalError || {}; + + if (throwOnError) { + throw finalError; + } + + // TODO: we probably want to return error and improve types + return responseStyle === 'data' + ? undefined + : { + error: finalError, + request, + response, + }; + } + }; + + const makeMethodFn = (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method }); + + const makeSseFn = (method: Uppercase) => async (options: RequestOptions) => { + const { opts, url } = await beforeRequest(options); + return createSseClient({ + ...opts, + body: opts.body as BodyInit | null | undefined, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + return request; + }, + serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined, + url, + }); + }; + + const _buildUrl: Client['buildUrl'] = (options) => buildUrl({ ..._config, ...options }); + + return { + buildUrl: _buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/frontend/src/shared/api/generated/client/index.ts b/frontend/src/shared/api/generated/client/index.ts new file mode 100644 index 0000000..b295ede --- /dev/null +++ b/frontend/src/shared/api/generated/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/frontend/src/shared/api/generated/client/types.gen.ts b/frontend/src/shared/api/generated/client/types.gen.ts new file mode 100644 index 0000000..4b288a5 --- /dev/null +++ b/frontend/src/shared/api/generated/client/types.gen.ts @@ -0,0 +1,217 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { Client as CoreClient, Config as CoreConfig } from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, CoreConfig { + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: 'arrayBuffer' | 'auto' | 'blob' | 'formData' | 'json' | 'stream' | 'text'; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> + extends + Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onRequest' + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + headers: Headers; + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record ? TData[keyof TData] : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? (TData extends Record ? TData[keyof TData] : TData) | undefined + : ( + | { + data: TData extends Record ? TData[keyof TData] : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record ? TError[keyof TError] : TError; + } + ) & { + /** request may be undefined, because error may be from building the request object itself */ + request?: Request; + /** response may be undefined, because error may be from building the request object itself or from a network error */ + response?: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick>, 'method'>, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: TData & Options, +) => string; + +export type Client = CoreClient & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + ([TData] extends [never] ? unknown : Omit); diff --git a/frontend/src/shared/api/generated/client/utils.gen.ts b/frontend/src/shared/api/generated/client/utils.gen.ts new file mode 100644 index 0000000..eb0164f --- /dev/null +++ b/frontend/src/shared/api/generated/client/utils.gen.ts @@ -0,0 +1,318 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; + +export const createQuerySerializer = ({ + parameters = {}, + ...args +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + const options = parameters[name] || args; + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved: options.allowReserved, + explode: true, + name, + style: 'form', + value, + ...options.array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved: options.allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...options.object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved: options.allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = (contentType: string | null): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if (cleanContent.startsWith('application/json') || cleanContent.endsWith('+json')) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => cleanContent.startsWith(type)) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e., their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +type ErrInterceptor = ( + error: Err, + /** response may be undefined due to a network error where no response object is produced */ + response: Res | undefined, + /** request may be undefined, because error may be from building the request object itself */ + request: Req | undefined, + options: Options, +) => Err | Promise; + +type ReqInterceptor = (request: Req, options: Options) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/frontend/src/shared/api/generated/core/auth.gen.ts b/frontend/src/shared/api/generated/core/auth.gen.ts new file mode 100644 index 0000000..3ebf994 --- /dev/null +++ b/frontend/src/shared/api/generated/core/auth.gen.ts @@ -0,0 +1,41 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/frontend/src/shared/api/generated/core/bodySerializer.gen.ts b/frontend/src/shared/api/generated/core/bodySerializer.gen.ts new file mode 100644 index 0000000..67daca6 --- /dev/null +++ b/frontend/src/shared/api/generated/core/bodySerializer.gen.ts @@ -0,0 +1,82 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ArrayStyle, ObjectStyle, SerializerOptions } from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: unknown) => unknown; + +type QuerySerializerOptionsObject = { + allowReserved?: boolean; + array?: Partial>; + object?: Partial>; +}; + +export type QuerySerializerOptions = QuerySerializerOptionsObject & { + /** + * Per-parameter serialization overrides. When provided, these settings + * override the global array/object settings for specific parameter names. + */ + parameters?: Record; +}; + +const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: (body: unknown): FormData => { + const data = new FormData(); + + Object.entries(body as Record).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: unknown): string => + JSON.stringify(body, (_key, value) => (typeof value === 'bigint' ? value.toString() : value)), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: (body: unknown): string => { + const data = new URLSearchParams(); + + Object.entries(body as Record).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/frontend/src/shared/api/generated/core/params.gen.ts b/frontend/src/shared/api/generated/core/params.gen.ts new file mode 100644 index 0000000..7955601 --- /dev/null +++ b/frontend/src/shared/api/generated/core/params.gen.ts @@ -0,0 +1,169 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + } + | { + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If `in` is omitted, `map` aliases `key` to the transport layer. + */ + map: Slot; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + | { + in: Slot; + map?: string; + } + | { + in?: never; + map: Slot; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if ('key' in config) { + map.set(config.key, { + map: config.map, + }); + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Array.isArray(value) && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = (args: ReadonlyArray, fields: FieldsConfig) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + if (field.in) { + (params[field.in] as Record)[name] = arg; + } + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + if (field.in) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + params[field.map] = value; + } + } else { + const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix)); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[key.slice(prefix.length)] = value; + } else if ('allowExtra' in config && config.allowExtra) { + for (const [slot, allowed] of Object.entries(config.allowExtra)) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/frontend/src/shared/api/generated/core/pathSerializer.gen.ts b/frontend/src/shared/api/generated/core/pathSerializer.gen.ts new file mode 100644 index 0000000..994b284 --- /dev/null +++ b/frontend/src/shared/api/generated/core/pathSerializer.gen.ts @@ -0,0 +1,171 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions extends SerializePrimitiveOptions, SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; +}; diff --git a/frontend/src/shared/api/generated/core/queryKeySerializer.gen.ts b/frontend/src/shared/api/generated/core/queryKeySerializer.gen.ts new file mode 100644 index 0000000..5000df6 --- /dev/null +++ b/frontend/src/shared/api/generated/core/queryKeySerializer.gen.ts @@ -0,0 +1,117 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * JSON-friendly union that mirrors what Pinia Colada can hash. + */ +export type JsonValue = + | null + | string + | number + | boolean + | JsonValue[] + | { [key: string]: JsonValue }; + +/** + * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes. + */ +export const queryKeyJsonReplacer = (_key: string, value: unknown) => { + if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { + return undefined; + } + if (typeof value === 'bigint') { + return value.toString(); + } + if (value instanceof Date) { + return value.toISOString(); + } + return value; +}; + +/** + * Safely stringifies a value and parses it back into a JsonValue. + */ +export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => { + try { + const json = JSON.stringify(input, queryKeyJsonReplacer); + if (json === undefined) { + return undefined; + } + return JSON.parse(json) as JsonValue; + } catch { + return undefined; + } +}; + +/** + * Detects plain objects (including objects with a null prototype). + */ +const isPlainObject = (value: unknown): value is Record => { + if (value === null || typeof value !== 'object') { + return false; + } + const prototype = Object.getPrototypeOf(value as object); + return prototype === Object.prototype || prototype === null; +}; + +/** + * Turns URLSearchParams into a sorted JSON object for deterministic keys. + */ +const serializeSearchParams = (params: URLSearchParams): JsonValue => { + const entries = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b)); + const result: Record = {}; + + for (const [key, value] of entries) { + const existing = result[key]; + if (existing === undefined) { + result[key] = value; + continue; + } + + if (Array.isArray(existing)) { + (existing as string[]).push(value); + } else { + result[key] = [existing, value]; + } + } + + return result; +}; + +/** + * Normalizes any accepted value into a JSON-friendly shape for query keys. + */ +export const serializeQueryKeyValue = (value: unknown): JsonValue | undefined => { + if (value === null) { + return null; + } + + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return value; + } + + if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { + return undefined; + } + + if (typeof value === 'bigint') { + return value.toString(); + } + + if (value instanceof Date) { + return value.toISOString(); + } + + if (Array.isArray(value)) { + return stringifyToJsonValue(value); + } + + if (typeof URLSearchParams !== 'undefined' && value instanceof URLSearchParams) { + return serializeSearchParams(value); + } + + if (isPlainObject(value)) { + return stringifyToJsonValue(value); + } + + return undefined; +}; diff --git a/frontend/src/shared/api/generated/core/serverSentEvents.gen.ts b/frontend/src/shared/api/generated/core/serverSentEvents.gen.ts new file mode 100644 index 0000000..ddf3c4d --- /dev/null +++ b/frontend/src/shared/api/generated/core/serverSentEvents.gen.ts @@ -0,0 +1,242 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export function createSseClient({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult { + let lastEventId: string | undefined; + + const sleep = sseSleepFn ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + buffer = buffer.replace(/\r\n?/g, '\n'); // normalize line endings + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt(line.replace(/^retry:\s*/, ''), 10); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if (sseMaxRetryAttempts !== undefined && attempt >= sseMaxRetryAttempts) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 30000); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +} diff --git a/frontend/src/shared/api/generated/core/types.gen.ts b/frontend/src/shared/api/generated/core/types.gen.ts new file mode 100644 index 0000000..9efe71d --- /dev/null +++ b/frontend/src/shared/api/generated/core/types.gen.ts @@ -0,0 +1,104 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] ? { sse?: never } : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + string | number | boolean | (string | number | boolean)[] | null | undefined | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g., converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true ? never : K]: T[K]; +}; diff --git a/frontend/src/shared/api/generated/core/utils.gen.ts b/frontend/src/shared/api/generated/core/utils.gen.ts new file mode 100644 index 0000000..9a4fec7 --- /dev/null +++ b/frontend/src/shared/api/generated/core/utils.gen.ts @@ -0,0 +1,140 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace(match, serializeArrayParam({ explode, name, style, value })); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e., client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/frontend/src/shared/api/generated/index.ts b/frontend/src/shared/api/generated/index.ts new file mode 100644 index 0000000..49050bf --- /dev/null +++ b/frontend/src/shared/api/generated/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export { getHealth, type Options } from './sdk.gen'; +export type { ClientOptions, ErrorCode, ErrorDetail, ErrorResponse, GetHealthData, GetHealthError, GetHealthErrors, GetHealthResponse, GetHealthResponses, HealthResponse, HealthStatus, RequestId, RequestIdHeader } from './types.gen'; diff --git a/frontend/src/shared/api/generated/sdk.gen.ts b/frontend/src/shared/api/generated/sdk.gen.ts new file mode 100644 index 0000000..018e288 --- /dev/null +++ b/frontend/src/shared/api/generated/sdk.gen.ts @@ -0,0 +1,26 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Client, Options as Options2, TDataShape } from './client'; +import { client } from './client.gen'; +import type { GetHealthData, GetHealthErrors, GetHealthResponses } from './types.gen'; + +export type Options = Options2 & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +/** + * Read API health. + * + * Returns privacy-safe API health with correlation evidence. + */ +export const getHealth = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/health', ...options }); diff --git a/frontend/src/shared/api/generated/types.gen.ts b/frontend/src/shared/api/generated/types.gen.ts new file mode 100644 index 0000000..b8b0054 --- /dev/null +++ b/frontend/src/shared/api/generated/types.gen.ts @@ -0,0 +1,65 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: 'http://localhost:8080' | (string & {}); +}; + +export type RequestId = string; + +export type HealthStatus = 'ok'; + +export type HealthResponse = { + status: HealthStatus; + service: 'juntly-api'; + version: string; + checkedAt: string; + requestId: RequestId; +}; + +export type ErrorCode = 'SERVICE_UNAVAILABLE'; + +export type ErrorDetail = { + code: ErrorCode; + message: 'Service unavailable'; + requestId: RequestId; +}; + +export type ErrorResponse = { + error: ErrorDetail; +}; + +/** + * Optional client-supplied correlation identifier. + */ +export type RequestIdHeader = RequestId; + +export type GetHealthData = { + body?: never; + headers?: { + /** + * Optional client-supplied correlation identifier. + */ + 'X-Request-ID'?: RequestId; + }; + path?: never; + query?: never; + url: '/api/v1/health'; +}; + +export type GetHealthErrors = { + /** + * API is unavailable. + */ + 503: ErrorResponse; +}; + +export type GetHealthError = GetHealthErrors[keyof GetHealthErrors]; + +export type GetHealthResponses = { + /** + * API is available. + */ + 200: HealthResponse; +}; + +export type GetHealthResponse = GetHealthResponses[keyof GetHealthResponses]; diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index 5e17841..5f88542 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -1,8 +1,14 @@ import react from "@vitejs/plugin-react"; +import { fileURLToPath } from "node:url"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [react()], + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, + }, test: { environment: "jsdom", setupFiles: ["./src/test/setup.ts"], diff --git a/openapi/juntly-api.v1.yaml b/openapi/juntly-api.v1.yaml new file mode 100644 index 0000000..97c316d --- /dev/null +++ b/openapi/juntly-api.v1.yaml @@ -0,0 +1,113 @@ +openapi: 3.1.0 +info: + title: Juntly API + version: 0.1.0 + summary: Versioned Juntly REST API contract. +servers: + - url: http://localhost:8080 + description: Local Go API +paths: + /api/v1/health: + get: + operationId: getHealth + summary: Read API health. + description: Returns privacy-safe API health with correlation evidence. + parameters: + - $ref: "#/components/parameters/RequestIdHeader" + responses: + "200": + description: API is available. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/HealthResponse" + "503": + description: API is unavailable. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" +components: + parameters: + RequestIdHeader: + name: X-Request-ID + in: header + required: false + description: Optional client-supplied correlation identifier. + schema: + $ref: "#/components/schemas/RequestId" + headers: + RequestIdHeader: + description: Correlation identifier attached to this response. + schema: + $ref: "#/components/schemas/RequestId" + schemas: + RequestId: + type: string + minLength: 8 + maxLength: 128 + pattern: "^[A-Za-z0-9._:-]+$" + examples: + - req_01juntlyhealth + HealthStatus: + type: string + enum: + - ok + HealthResponse: + type: object + additionalProperties: false + required: + - status + - service + - version + - checkedAt + - requestId + properties: + status: + $ref: "#/components/schemas/HealthStatus" + service: + type: string + const: juntly-api + version: + type: string + minLength: 1 + examples: + - 0.1.0 + checkedAt: + type: string + format: date-time + requestId: + $ref: "#/components/schemas/RequestId" + ErrorCode: + type: string + enum: + - SERVICE_UNAVAILABLE + ErrorDetail: + type: object + additionalProperties: false + required: + - code + - message + - requestId + properties: + code: + $ref: "#/components/schemas/ErrorCode" + message: + type: string + const: Service unavailable + requestId: + $ref: "#/components/schemas/RequestId" + ErrorResponse: + type: object + additionalProperties: false + required: + - error + properties: + error: + $ref: "#/components/schemas/ErrorDetail" From e154f44d1aba9fee342104fb1ac210053b0f8703 Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Fri, 21 Aug 2026 11:35:08 +0100 Subject: [PATCH 02/64] feat: add localized Clerk identity foundation --- README.md | 3 +- context/architecture.md | 4 +- context/stack.md | 2 +- frontend/.env.example | 2 + frontend/.gitignore | 1 + frontend/README.md | 23 ++- frontend/messages/en.json | 8 + frontend/messages/es.json | 8 + frontend/messages/pt-PT.json | 8 + frontend/package-lock.json | 148 +++++++++++++++++- frontend/package.json | 4 + .../src/app/[locale]/account/page.test.tsx | 50 ++++++ frontend/src/app/[locale]/account/page.tsx | 37 +++++ frontend/src/app/[locale]/layout.tsx | 17 +- frontend/src/app/[locale]/page.tsx | 8 +- .../[locale]/sign-in/[[...sign-in]]/page.tsx | 26 +++ .../[locale]/sign-up/[[...sign-up]]/page.tsx | 26 +++ .../features/auth/auth-navigation.test.tsx | 60 +++++++ .../src/features/auth/auth-navigation.tsx | 41 +++++ .../features/auth/clerk-localization.test.ts | 12 ++ .../src/features/auth/clerk-localization.ts | 14 ++ .../src/features/auth/require-session.test.ts | 42 +++++ frontend/src/features/auth/require-session.ts | 16 ++ .../landing/components/landing-shell.test.tsx | 35 ++++- .../landing/components/landing-shell.tsx | 24 ++- frontend/src/local-origin.test.ts | 27 ++++ frontend/src/proxy.test.ts | 59 +++++++ frontend/src/proxy.ts | 22 ++- frontend/vitest.config.ts | 7 + 29 files changed, 712 insertions(+), 22 deletions(-) create mode 100644 frontend/.env.example create mode 100644 frontend/src/app/[locale]/account/page.test.tsx create mode 100644 frontend/src/app/[locale]/account/page.tsx create mode 100644 frontend/src/app/[locale]/sign-in/[[...sign-in]]/page.tsx create mode 100644 frontend/src/app/[locale]/sign-up/[[...sign-up]]/page.tsx create mode 100644 frontend/src/features/auth/auth-navigation.test.tsx create mode 100644 frontend/src/features/auth/auth-navigation.tsx create mode 100644 frontend/src/features/auth/clerk-localization.test.ts create mode 100644 frontend/src/features/auth/clerk-localization.ts create mode 100644 frontend/src/features/auth/require-session.test.ts create mode 100644 frontend/src/features/auth/require-session.ts create mode 100644 frontend/src/local-origin.test.ts create mode 100644 frontend/src/proxy.test.ts diff --git a/README.md b/README.md index b4998cc..b0ae02d 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,10 @@ This repository is at the foundation stage. The initial delivery contains: - Durable product, architecture, security, UI, workflow, and decision context. - A localized responsive Next.js frontend shell under `frontend/` after the scaffold commit. - pt-PT default, English support, and Spanish-ready routing/messages. +- A source-level Clerk frontend identity foundation: localized sign-in/sign-up routes, session-aware navigation, and a server-enforced account route. - Frontend test, format, lint, type, build, audit, CI, and runtime-verification foundations. -It does **not** yet implement accounts, provider profiles, listings, search, chat, quotations, bookings, reviews, payments, Go/OpenAPI, Clerk, Supabase, Redis, object storage, Docker, or production deployment. +The Clerk source, local quality gates, canonical-origin runtime routes, and signed-out account redirect are verified. An authenticated browser-session journey still requires a real test user. Durable Go internal-user mapping, provider profiles, listings, search, chat, quotations, bookings, reviews, payments, Go/OpenAPI, Supabase, Redis, object storage, Docker, and production deployment are not implemented yet. ## Repository layout diff --git a/context/architecture.md b/context/architecture.md index 4e4d557..9909166 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -10,7 +10,7 @@ juntly/ └── supabase/ project-owned PostgreSQL/PostGIS configuration (later slice) ``` -Only `frontend/` is created during the initial bootstrap. Empty future-runtime directories are prohibited. +Only `frontend/` is created during the initial bootstrap. The Clerk frontend identity foundation is implemented in that application; the Go API and durable internal-user mapping remain absent until their parent API slice is integrated. Empty future-runtime directories are prohibited. ## Approved request path @@ -41,7 +41,7 @@ Future modules use domain → application → ports → adapters dependency dire ## Authentication and authorization -Clerk owns primary email/password identity and session lifecycle. The BFF verifies Clerk sessions. Go maps each verified Clerk subject uniquely to an opaque internal user and enforces platform role, provider/customer domain role, entitlement, ownership, resource membership, and administrative policy. UI visibility is presentation only. +Clerk owns primary email/password identity and session lifecycle. The frontend provides localized Clerk entry routes and resource-local server session enforcement; UI visibility is presentation only. Local browser-facing Next.js routes use the canonical `localhost:4200` origin, because mixing loopback aliases can turn Clerk continuation rewrites into recursive external proxies. Once the API parent is integrated, Go will map each verified Clerk subject uniquely to an opaque internal user and enforce platform role, provider/customer domain role, entitlement, ownership, resource membership, and administrative policy. A real authenticated browser session remains a separate verification gate. ## Data and integration ownership diff --git a/context/stack.md b/context/stack.md index 8f06f40..c1ebc95 100644 --- a/context/stack.md +++ b/context/stack.md @@ -33,4 +33,4 @@ ## Current implementation boundary -The bootstrap implements context and the Next.js frontend shell only. Go, OpenAPI generation, BFF proxying, Clerk, Supabase, Redis, object storage, background workers, payments, and Docker runtime topology are not yet implemented and must not be represented as working. +The current feature branch implements the Next.js shell plus a Clerk frontend identity/session foundation. Its local tests, typecheck, production build, audit, canonical-origin dynamic routes, and signed-out redirect pass. A real authenticated browser session remains separate evidence requiring a test user. Durable Go internal-user mapping, Go/OpenAPI generation, BFF proxying, Supabase, Redis, object storage, background workers, payments, and Docker runtime topology are not yet implemented and must not be represented as working. diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..eacd76b --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,2 @@ +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= +CLERK_SECRET_KEY= diff --git a/frontend/.gitignore b/frontend/.gitignore index 5ef6a52..7b8da95 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -32,6 +32,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +!.env.example # vercel .vercel diff --git a/frontend/README.md b/frontend/README.md index 3d6aa5e..6d44022 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -4,14 +4,15 @@ Localized, mobile-first Next.js shell for Juntly. ## Current scope -This bootstrap intentionally provides only: +This foundation currently provides: - pt-PT default routing, English support, and Spanish-ready translations. - Localized metadata and route boundaries. - A responsive, accessible product-introduction shell. +- Clerk account entry, locale-aware in-app authentication forms, and a fail-closed session boundary at `/:locale/account`. - Test, format, lint, type, build, dependency-audit, and CI foundations. -Authentication, listings, search, chat, quotations, bookings, payments, the Go API, and external infrastructure are not implemented yet. +Provider/customer profiles, Go internal-user mapping, listings, search, chat, quotations, bookings, payments, and external infrastructure are not implemented yet. ## Requirements @@ -27,13 +28,27 @@ npm test npm run verify ``` +## Local Clerk configuration + +The Clerk CLI links the local checkout and writes credentials only to ignored +`frontend/.env.local`. Copy `frontend/.env.example` only when configuring a +separate development environment; never commit real key values. The app uses +first-party `/:locale/sign-in` and `/:locale/sign-up` routes, and the protected +`/:locale/account` route independently verifies the server-side session. + +The source-level integration and signed-out runtime route behavior pass local +verification. Use `localhost` consistently for the browser and Next.js bind +hostname: mixing it with `127.0.0.1` can make Clerk continuation rewrites look +external and recurse through Next's proxy. An authenticated browser session still +requires a real test user; a successful build alone is not auth proof. + For a production runtime probe: ```bash npm run build -npm run start -- --hostname 127.0.0.1 --port 4200 +npm run start:local ``` -Then open `http://127.0.0.1:4200/`; locale routing redirects to pt-PT by default. +Then open `http://localhost:4200/`; locale routing redirects to pt-PT by default. Project-wide architecture and product rules live in `../context/`. diff --git a/frontend/messages/en.json b/frontend/messages/en.json index d8d0d9f..27b8da8 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -14,6 +14,14 @@ "visionDescription": "Discovery, contact and trust without taking choice away from people. Juntly connects those who need help with those who know how.", "footerLabel": "Juntly — born in Portugal." }, + "Auth": { + "signIn": "Sign in", + "signUp": "Create account" + }, + "Account": { + "title": "Juntly account", + "description": "Your session is active. Marketplace profiles and features will appear here as they are implemented." + }, "Errors": { "title": "Something went wrong", "description": "We could not load this page. Please try again.", diff --git a/frontend/messages/es.json b/frontend/messages/es.json index e95883e..8d98654 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -14,6 +14,14 @@ "visionDescription": "Descubrimiento, contacto y confianza sin quitar elección a las personas. Juntly conecta a quien necesita ayuda con quien sabe hacerla.", "footerLabel": "Juntly — nacida en Portugal." }, + "Auth": { + "signIn": "Iniciar sesión", + "signUp": "Crear cuenta" + }, + "Account": { + "title": "Cuenta Juntly", + "description": "Tu sesión está activa. Los perfiles y funciones del marketplace aparecerán aquí a medida que se implementen." + }, "Errors": { "title": "Algo salió mal", "description": "No hemos podido cargar esta página. Inténtalo de nuevo.", diff --git a/frontend/messages/pt-PT.json b/frontend/messages/pt-PT.json index e5682b6..4576719 100644 --- a/frontend/messages/pt-PT.json +++ b/frontend/messages/pt-PT.json @@ -14,6 +14,14 @@ "visionDescription": "Descoberta, contacto e confiança sem retirar a escolha às pessoas. A Juntly liga quem precisa a quem sabe fazer.", "footerLabel": "Juntly — com origem em Portugal." }, + "Auth": { + "signIn": "Entrar", + "signUp": "Criar conta" + }, + "Account": { + "title": "Conta Juntly", + "description": "A sua sessão está ativa. Os perfis e funcionalidades do marketplace serão disponibilizados aqui à medida que forem implementados." + }, "Errors": { "title": "Algo não correu bem", "description": "Não foi possível carregar esta página. Tente novamente.", diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a1a6baa..5846c0e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,8 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "@clerk/localizations": "4.15.4", + "@clerk/nextjs": "7.7.9", "next": "16.3.1", "next-intl": "4.13.6", "react": "19.2.8", @@ -365,6 +367,97 @@ "specificity": "bin/cli.js" } }, + "node_modules/@clerk/backend": { + "version": "3.16.9", + "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-3.16.9.tgz", + "integrity": "sha512-x+kyjPTAMNEJIVuRMEXRSZUJPnB2PAoljSQ5gh/O/nVmRPIF1fU+tMQYEHKiCg3Ab/JUSywOsgamoEufyJj8dA==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^4.29.2", + "standardwebhooks": "^1.0.0", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.9.0" + } + }, + "node_modules/@clerk/localizations": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/@clerk/localizations/-/localizations-4.15.4.tgz", + "integrity": "sha512-CX78op+NLxAPigWH6oq9PF98ATOz3q3zVzE0msg5AfiCu6xNgzOLTqTKLNGF8DaFwsAxQ18pCRBaB7UAU4fXDA==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^4.29.2" + }, + "engines": { + "node": ">=20.9.0" + } + }, + "node_modules/@clerk/nextjs": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@clerk/nextjs/-/nextjs-7.7.9.tgz", + "integrity": "sha512-6niXLthsXYDwmH2pD0qL7Ut499p6/ucect1Ks9WQp+rVC5va+rTYGEy9pkKKWWePnO4oYf7ncmzJmjFYZXM0Hg==", + "license": "MIT", + "dependencies": { + "@clerk/backend": "^3.16.9", + "@clerk/react": "^6.14.4", + "@clerk/shared": "^4.29.2", + "server-only": "0.0.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "peerDependencies": { + "next": "^15.2.8 || ^15.3.8 || ^15.4.10 || ^15.5.9 || ^15.6.0-0 || ^16.0.10 || ^16.1.0-0", + "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", + "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" + } + }, + "node_modules/@clerk/react": { + "version": "6.14.4", + "resolved": "https://registry.npmjs.org/@clerk/react/-/react-6.14.4.tgz", + "integrity": "sha512-vMv3SU8dvo/b09/FAY7A0xaAe+YhZ2u4nvkRUUdqmOi8u3HNSJ3s2k2PIb0cNHsLurMvDzw/dD2Hbk97MPnN/A==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^4.29.2", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", + "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" + } + }, + "node_modules/@clerk/shared": { + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-4.29.2.tgz", + "integrity": "sha512-9c9Mc1oqumsqo+JY5R37O1ipwcG3RmwPK9oadBLL9E4VxZXthXVaFI9u+1/I4BSFGA/B9BO+17tkVDL4G0Wpbg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "^5.100.6", + "dequal": "2.0.3", + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.7" + }, + "engines": { + "node": ">=20.9.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", + "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/@csstools/color-helpers": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz", @@ -2148,6 +2241,12 @@ "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", "license": "MIT" }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -2682,6 +2781,16 @@ "tailwindcss": "4.3.3" } }, + "node_modules/@tanstack/query-core": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", + "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -4123,9 +4232,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -5178,6 +5285,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -5430,6 +5543,12 @@ "node": ">=10.13.0" } }, + "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==", + "license": "BSD-2-Clause" + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -6169,6 +6288,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-cookie": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.7.tgz", + "integrity": "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7777,6 +7905,12 @@ "semver": "bin/semver.js" } }, + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -8018,6 +8152,16 @@ "dev": true, "license": "MIT" }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/std-env": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9d45f49..0836361 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,8 +5,10 @@ "type": "module", "scripts": { "dev": "next dev", + "dev:local": "next dev --hostname localhost --port 4200", "build": "next build", "start": "next start", + "start:local": "next start --hostname localhost --port 4200", "format": "prettier --write .", "format:check": "prettier --check .", "test": "vitest run", @@ -16,6 +18,8 @@ "verify": "npm run format:check && npm test && npm run lint && npm run typecheck && npm run build && npm audit --audit-level=high" }, "dependencies": { + "@clerk/localizations": "4.15.4", + "@clerk/nextjs": "7.7.9", "next": "16.3.1", "next-intl": "4.13.6", "react": "19.2.8", diff --git a/frontend/src/app/[locale]/account/page.test.tsx b/frontend/src/app/[locale]/account/page.test.tsx new file mode 100644 index 0000000..8dfebd9 --- /dev/null +++ b/frontend/src/app/[locale]/account/page.test.tsx @@ -0,0 +1,50 @@ +import { render, screen } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + getTranslations: vi.fn(), + requireAuthenticatedUser: vi.fn(), +})); + +vi.mock("next-intl/server", () => ({ + getTranslations: mocks.getTranslations, +})); +vi.mock("@/features/auth/require-session", () => ({ + requireAuthenticatedUser: mocks.requireAuthenticatedUser, +})); + +import AccountPage, { dynamic } from "./page"; + +afterEach(() => { + mocks.getTranslations.mockReset(); + mocks.requireAuthenticatedUser.mockReset(); +}); + +describe("AccountPage", () => { + it("is explicitly dynamically rendered because it resolves request-scoped identity", () => { + expect(dynamic).toBe("force-dynamic"); + }); + + it("requires a verified session before rendering the localized account confirmation", async () => { + mocks.requireAuthenticatedUser.mockResolvedValue("user_verified_subject"); + mocks.getTranslations.mockResolvedValue( + (key: string) => + ({ + description: "A sua sessão está ativa.", + title: "Conta Juntly", + })[key], + ); + + render( + await AccountPage({ + params: Promise.resolve({ locale: "pt-PT" }), + } as never), + ); + + expect(mocks.requireAuthenticatedUser).toHaveBeenCalledWith("pt-PT"); + expect( + screen.getByRole("heading", { name: "Conta Juntly" }), + ).toBeInTheDocument(); + expect(screen.getByText("A sua sessão está ativa.")).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/app/[locale]/account/page.tsx b/frontend/src/app/[locale]/account/page.tsx new file mode 100644 index 0000000..05eae94 --- /dev/null +++ b/frontend/src/app/[locale]/account/page.tsx @@ -0,0 +1,37 @@ +import { getTranslations } from "next-intl/server"; +import { hasLocale } from "next-intl"; +import { notFound } from "next/navigation"; + +import { requireAuthenticatedUser } from "@/features/auth/require-session"; +import { routing } from "@/i18n/routing"; + +export const dynamic = "force-dynamic"; + +type AccountPageProps = { + params: Promise<{ locale: string }>; +}; + +export default async function AccountPage({ params }: AccountPageProps) { + const { locale } = await params; + + if (!hasLocale(routing.locales, locale)) { + notFound(); + } + + await requireAuthenticatedUser(locale); + const t = await getTranslations("Account"); + + return ( +
+
+

+ Juntly +

+

+ {t("title")} +

+

{t("description")}

+
+
+ ); +} diff --git a/frontend/src/app/[locale]/layout.tsx b/frontend/src/app/[locale]/layout.tsx index 519fb4f..8b5d7e8 100644 --- a/frontend/src/app/[locale]/layout.tsx +++ b/frontend/src/app/[locale]/layout.tsx @@ -1,9 +1,11 @@ +import { ClerkProvider } from "@clerk/nextjs"; import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import { hasLocale, NextIntlClientProvider } from "next-intl"; import { getMessages, getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; +import { getClerkLocalization } from "@/features/auth/clerk-localization"; import { routing } from "@/i18n/routing"; import "../globals.css"; @@ -66,9 +68,18 @@ export default async function LocaleLayout({ className={`${geistSans.variable} ${geistMono.variable}`} > - - {children} - + + + {children} + + ); diff --git a/frontend/src/app/[locale]/page.tsx b/frontend/src/app/[locale]/page.tsx index 99934e7..f7ee04f 100644 --- a/frontend/src/app/[locale]/page.tsx +++ b/frontend/src/app/[locale]/page.tsx @@ -1,8 +1,10 @@ -import { getTranslations } from "next-intl/server"; +import { getLocale, getTranslations } from "next-intl/server"; import { LandingShell } from "@/features/landing/components/landing-shell"; export default async function HomePage() { + const locale = await getLocale(); + const auth = await getTranslations("Auth"); const t = await getTranslations("Landing"); return ( @@ -12,6 +14,10 @@ export default async function HomePage() { heading={t("heading")} description={t("description")} statusLabel={t("statusLabel")} + signInLabel={auth("signIn")} + signInUrl={`/${locale}/sign-in`} + signUpLabel={auth("signUp")} + signUpUrl={`/${locale}/sign-up`} visionLinkLabel={t("visionLinkLabel")} visionTitle={t("visionTitle")} visionDescription={t("visionDescription")} diff --git a/frontend/src/app/[locale]/sign-in/[[...sign-in]]/page.tsx b/frontend/src/app/[locale]/sign-in/[[...sign-in]]/page.tsx new file mode 100644 index 0000000..5a7fc55 --- /dev/null +++ b/frontend/src/app/[locale]/sign-in/[[...sign-in]]/page.tsx @@ -0,0 +1,26 @@ +import { SignIn } from "@clerk/nextjs"; +import { hasLocale } from "next-intl"; +import { notFound } from "next/navigation"; + +import { routing } from "@/i18n/routing"; + +type AuthPageProps = { + params: Promise<{ locale: string }>; +}; + +export default async function SignInPage({ params }: AuthPageProps) { + const { locale } = await params; + + if (!hasLocale(routing.locales, locale)) { + notFound(); + } + + return ( +
+ +
+ ); +} diff --git a/frontend/src/app/[locale]/sign-up/[[...sign-up]]/page.tsx b/frontend/src/app/[locale]/sign-up/[[...sign-up]]/page.tsx new file mode 100644 index 0000000..c4c8649 --- /dev/null +++ b/frontend/src/app/[locale]/sign-up/[[...sign-up]]/page.tsx @@ -0,0 +1,26 @@ +import { SignUp } from "@clerk/nextjs"; +import { hasLocale } from "next-intl"; +import { notFound } from "next/navigation"; + +import { routing } from "@/i18n/routing"; + +type AuthPageProps = { + params: Promise<{ locale: string }>; +}; + +export default async function SignUpPage({ params }: AuthPageProps) { + const { locale } = await params; + + if (!hasLocale(routing.locales, locale)) { + notFound(); + } + + return ( +
+ +
+ ); +} diff --git a/frontend/src/features/auth/auth-navigation.test.tsx b/frontend/src/features/auth/auth-navigation.test.tsx new file mode 100644 index 0000000..0d6cb12 --- /dev/null +++ b/frontend/src/features/auth/auth-navigation.test.tsx @@ -0,0 +1,60 @@ +import type { ReactNode } from "react"; + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +const clerkState = vi.hoisted(() => ({ signedIn: false })); + +vi.mock("@clerk/nextjs", () => ({ + Show: ({ children, when }: { children: ReactNode; when: string }) => + (when === "signed-in" ? clerkState.signedIn : !clerkState.signedIn) + ? children + : null, + UserButton: (props: Record) => { + if ("afterSignOutUrl" in props) { + throw new Error("UserButton does not accept afterSignOutUrl"); + } + + return User menu; + }, +})); + +import { AuthNavigation } from "./auth-navigation"; + +const copy = { + signInLabel: "Entrar", + signInUrl: "/pt-PT/sign-in", + signUpLabel: "Criar conta", + signUpUrl: "/pt-PT/sign-up", +}; + +describe("AuthNavigation", () => { + it("renders localized touch-safe sign-in and sign-up links for signed-out visitors", () => { + clerkState.signedIn = false; + render(); + + expect( + screen.getByRole("link", { name: copy.signInLabel }), + ).toHaveAttribute("href", copy.signInUrl); + expect( + screen.getByRole("link", { name: copy.signUpLabel }), + ).toHaveAttribute("href", copy.signUpUrl); + expect(screen.getByRole("link", { name: copy.signInLabel })).toHaveClass( + "min-h-11", + ); + expect(screen.queryByText("User menu")).not.toBeInTheDocument(); + }); + + it("renders only the account menu for signed-in visitors without an unsupported redirect prop", () => { + clerkState.signedIn = true; + render(); + + expect( + screen.queryByRole("link", { name: copy.signInLabel }), + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("link", { name: copy.signUpLabel }), + ).not.toBeInTheDocument(); + expect(screen.getByText("User menu")).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/features/auth/auth-navigation.tsx b/frontend/src/features/auth/auth-navigation.tsx new file mode 100644 index 0000000..a9eec8e --- /dev/null +++ b/frontend/src/features/auth/auth-navigation.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { Show, UserButton } from "@clerk/nextjs"; + +type AuthNavigationProps = { + signInLabel: string; + signInUrl: string; + signUpLabel: string; + signUpUrl: string; +}; + +export function AuthNavigation({ + signInLabel, + signInUrl, + signUpLabel, + signUpUrl, +}: AuthNavigationProps) { + return ( + + ); +} diff --git a/frontend/src/features/auth/clerk-localization.test.ts b/frontend/src/features/auth/clerk-localization.test.ts new file mode 100644 index 0000000..82c4e00 --- /dev/null +++ b/frontend/src/features/auth/clerk-localization.test.ts @@ -0,0 +1,12 @@ +import { enUS, esES, ptPT } from "@clerk/localizations"; +import { describe, expect, it } from "vitest"; + +import { getClerkLocalization } from "./clerk-localization"; + +describe("getClerkLocalization", () => { + it("maps every supported Juntly locale to the matching Clerk localization", () => { + expect(getClerkLocalization("pt-PT")).toBe(ptPT); + expect(getClerkLocalization("en")).toBe(enUS); + expect(getClerkLocalization("es")).toBe(esES); + }); +}); diff --git a/frontend/src/features/auth/clerk-localization.ts b/frontend/src/features/auth/clerk-localization.ts new file mode 100644 index 0000000..ffa5e64 --- /dev/null +++ b/frontend/src/features/auth/clerk-localization.ts @@ -0,0 +1,14 @@ +import { enUS, esES, ptPT } from "@clerk/localizations"; + +import type { AppLocale } from "@/i18n/routing"; + +export function getClerkLocalization(locale: AppLocale) { + switch (locale) { + case "pt-PT": + return ptPT; + case "en": + return enUS; + case "es": + return esES; + } +} diff --git a/frontend/src/features/auth/require-session.test.ts b/frontend/src/features/auth/require-session.test.ts new file mode 100644 index 0000000..0a648bb --- /dev/null +++ b/frontend/src/features/auth/require-session.test.ts @@ -0,0 +1,42 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + auth: vi.fn(), + redirect: vi.fn(), +})); + +vi.mock("@clerk/nextjs/server", () => ({ auth: mocks.auth })); +vi.mock("next/navigation", () => ({ redirect: mocks.redirect })); + +import { requireAuthenticatedUser } from "./require-session"; + +afterEach(() => { + mocks.auth.mockReset(); + mocks.redirect.mockReset(); +}); + +describe("requireAuthenticatedUser", () => { + it("fails closed by redirecting a signed-out visitor to the localized sign-in route", async () => { + mocks.auth.mockResolvedValue({ isAuthenticated: false, userId: null }); + mocks.redirect.mockImplementation(() => { + throw new Error("redirected"); + }); + + await expect(requireAuthenticatedUser("pt-PT")).rejects.toThrow( + "redirected", + ); + expect(mocks.redirect).toHaveBeenCalledWith("/pt-PT/sign-in"); + }); + + it("returns only the verified opaque Clerk subject for an authenticated session", async () => { + mocks.auth.mockResolvedValue({ + isAuthenticated: true, + userId: "user_verified_subject", + }); + + await expect(requireAuthenticatedUser("en")).resolves.toBe( + "user_verified_subject", + ); + expect(mocks.redirect).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/src/features/auth/require-session.ts b/frontend/src/features/auth/require-session.ts new file mode 100644 index 0000000..be27ae2 --- /dev/null +++ b/frontend/src/features/auth/require-session.ts @@ -0,0 +1,16 @@ +import { auth } from "@clerk/nextjs/server"; +import { redirect } from "next/navigation"; + +import type { AppLocale } from "@/i18n/routing"; + +export async function requireAuthenticatedUser( + locale: AppLocale, +): Promise { + const { isAuthenticated, userId } = await auth(); + + if (!isAuthenticated || !userId) { + redirect(`/${locale}/sign-in`); + } + + return userId; +} diff --git a/frontend/src/features/landing/components/landing-shell.test.tsx b/frontend/src/features/landing/components/landing-shell.test.tsx index e4db5bd..f3eb873 100644 --- a/frontend/src/features/landing/components/landing-shell.test.tsx +++ b/frontend/src/features/landing/components/landing-shell.test.tsx @@ -1,5 +1,24 @@ import { render, screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; + +vi.mock("@/features/auth/auth-navigation", () => ({ + AuthNavigation: ({ + signInLabel, + signInUrl, + signUpLabel, + signUpUrl, + }: { + signInLabel: string; + signInUrl: string; + signUpLabel: string; + signUpUrl: string; + }) => ( + + ), +})); import { LandingShell } from "./landing-shell"; @@ -9,6 +28,10 @@ const copy = { heading: "Serviços locais, mais perto de si.", description: "Uma forma simples de encontrar pessoas com competências reais.", statusLabel: "A plataforma está a nascer.", + signInLabel: "Entrar", + signInUrl: "/pt-PT/sign-in", + signUpLabel: "Criar conta", + signUpUrl: "/pt-PT/sign-up", visionLinkLabel: "Conhecer a visão", visionTitle: "Criada para ligações locais reais", visionDescription: @@ -41,12 +64,18 @@ describe("LandingShell", () => { ).toBeInTheDocument(); }); - it("does not advertise unavailable marketplace actions", () => { + it("offers localized account entry points without advertising marketplace actions", () => { render(); + expect( + screen.getByRole("link", { name: copy.signInLabel }), + ).toHaveAttribute("href", copy.signInUrl); + expect( + screen.getByRole("link", { name: copy.signUpLabel }), + ).toHaveAttribute("href", copy.signUpUrl); expect( screen.queryByRole("button", { - name: /sign in|register|publish|buy|checkout/i, + name: /publish|buy|checkout/i, }), ).not.toBeInTheDocument(); }); diff --git a/frontend/src/features/landing/components/landing-shell.tsx b/frontend/src/features/landing/components/landing-shell.tsx index 00b7b06..851dd8c 100644 --- a/frontend/src/features/landing/components/landing-shell.tsx +++ b/frontend/src/features/landing/components/landing-shell.tsx @@ -1,9 +1,15 @@ +import { AuthNavigation } from "@/features/auth/auth-navigation"; + type LandingShellProps = { eyebrow: string; tagline: string; heading: string; description: string; statusLabel: string; + signInLabel: string; + signInUrl: string; + signUpLabel: string; + signUpUrl: string; visionLinkLabel: string; visionTitle: string; visionDescription: string; @@ -16,6 +22,10 @@ export function LandingShell({ heading, description, statusLabel, + signInLabel, + signInUrl, + signUpLabel, + signUpUrl, visionLinkLabel, visionTitle, visionDescription, @@ -32,9 +42,17 @@ export function LandingShell({ > Juntly. -

- {eyebrow} -

+
+

+ {eyebrow} +

+ +
diff --git a/frontend/src/local-origin.test.ts b/frontend/src/local-origin.test.ts new file mode 100644 index 0000000..8dc02ed --- /dev/null +++ b/frontend/src/local-origin.test.ts @@ -0,0 +1,27 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +import { describe, expect, it } from "vitest"; + +type PackageManifest = { + scripts?: Record; +}; + +function readPackageManifest(): PackageManifest { + return JSON.parse( + readFileSync(resolve(process.cwd(), "package.json"), "utf8"), + ) as PackageManifest; +} + +describe("local browser origin", () => { + it("provides localhost-bound development and production launch scripts", () => { + const manifest = readPackageManifest(); + + expect(manifest.scripts?.["dev:local"]).toBe( + "next dev --hostname localhost --port 4200", + ); + expect(manifest.scripts?.["start:local"]).toBe( + "next start --hostname localhost --port 4200", + ); + }); +}); diff --git a/frontend/src/proxy.test.ts b/frontend/src/proxy.test.ts new file mode 100644 index 0000000..f53d698 --- /dev/null +++ b/frontend/src/proxy.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => { + const apiResponse = { source: "api" }; + const intlResponse = { source: "intl" }; + + return { + apiResponse, + clerkMiddleware: vi.fn( + (handler: (...args: unknown[]) => unknown) => handler, + ), + createIntlMiddleware: vi.fn(() => vi.fn(() => intlResponse)), + intlResponse, + nextResponse: vi.fn(() => apiResponse), + }; +}); + +vi.mock("@clerk/nextjs/server", () => ({ + clerkMiddleware: mocks.clerkMiddleware, +})); +vi.mock("next-intl/middleware", () => ({ + default: mocks.createIntlMiddleware, +})); +vi.mock("next/server", () => ({ + NextResponse: { next: mocks.nextResponse }, +})); + +type ProxyHandler = ( + auth: unknown, + request: { nextUrl: { pathname: string } }, +) => Promise | unknown; + +async function loadProxy() { + vi.resetModules(); + return import("./proxy"); +} + +describe("proxy", () => { + it("composes Clerk with locale routing and matches API routes", async () => { + const { config } = await loadProxy(); + + expect(mocks.clerkMiddleware).toHaveBeenCalledOnce(); + expect(config.matcher).toContain("/(api|trpc)(.*)"); + }); + + it("does not send API requests through locale routing", async () => { + const { default: proxy } = await loadProxy(); + const clerkProxy = proxy as unknown as ProxyHandler; + + await expect( + clerkProxy({}, { nextUrl: { pathname: "/api/v1/health" } }), + ).resolves.toBe(mocks.apiResponse); + expect(mocks.nextResponse).toHaveBeenCalledOnce(); + + await expect( + clerkProxy({}, { nextUrl: { pathname: "/pt-PT" } }), + ).resolves.toBe(mocks.intlResponse); + }); +}); diff --git a/frontend/src/proxy.ts b/frontend/src/proxy.ts index 5cc2354..49515da 100644 --- a/frontend/src/proxy.ts +++ b/frontend/src/proxy.ts @@ -1,9 +1,27 @@ +import { clerkMiddleware } from "@clerk/nextjs/server"; +import { NextResponse } from "next/server"; import createMiddleware from "next-intl/middleware"; import { routing } from "./i18n/routing"; -export default createMiddleware(routing); +const intlMiddleware = createMiddleware(routing); + +export default clerkMiddleware(async (_auth, request) => { + if ( + request.nextUrl.pathname === "/api" || + request.nextUrl.pathname.startsWith("/api/") || + request.nextUrl.pathname.startsWith("/__clerk/") + ) { + return NextResponse.next(); + } + + return intlMiddleware(request); +}); export const config = { - matcher: "/((?!api|_next|_vercel|.*\\..*).*)", + matcher: [ + "/((?!_next|_vercel|.*\\..*).*)", + "/(api|trpc)(.*)", + "/__clerk/(.*)", + ], }; diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index 5e17841..ed841c3 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -1,8 +1,15 @@ +import { fileURLToPath } from "node:url"; + import react from "@vitejs/plugin-react"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [react()], + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, + }, test: { environment: "jsdom", setupFiles: ["./src/test/setup.ts"], From d0cf80c2e4aef40e6c19f177e80514d10ed5f422 Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Fri, 21 Aug 2026 13:09:15 +0100 Subject: [PATCH 03/64] feat: add durable internal user mapping --- backend/.env.example | 7 + backend/cmd/api/config.go | 33 + backend/cmd/api/config_test.go | 66 ++ backend/cmd/api/main.go | 34 +- backend/ent/client.go | 341 ++++++++++ backend/ent/ent.go | 608 ++++++++++++++++++ backend/ent/entc.go | 3 + backend/ent/enttest/enttest.go | 84 +++ backend/ent/hook/hook.go | 199 ++++++ backend/ent/internaluser.go | 129 ++++ backend/ent/internaluser/internaluser.go | 79 +++ backend/ent/internaluser/where.go | 231 +++++++ backend/ent/internaluser_create.go | 269 ++++++++ backend/ent/internaluser_delete.go | 88 +++ backend/ent/internaluser_query.go | 528 +++++++++++++++ backend/ent/internaluser_update.go | 212 ++++++ backend/ent/migrate/migrate.go | 64 ++ backend/ent/migrate/schema.go | 35 + backend/ent/mutation.go | 469 ++++++++++++++ backend/ent/predicate/predicate.go | 10 + backend/ent/runtime.go | 51 ++ backend/ent/runtime/runtime.go | 10 + backend/ent/schema/internaluser.go | 34 + backend/ent/schema/internaluser_test.go | 40 ++ backend/ent/tx.go | 210 ++++++ backend/go.mod | 30 + backend/go.sum | 115 ++++ backend/internal/authn/clerk_verifier.go | 85 +++ backend/internal/authn/clerk_verifier_test.go | 68 ++ backend/internal/authn/middleware.go | 99 +++ backend/internal/authn/middleware_test.go | 186 ++++++ backend/internal/authn/verifier.go | 11 + backend/internal/httpapi/health_handler.go | 4 +- .../internal/httpapi/openapi_contract_test.go | 39 ++ backend/internal/httpapi/reconcile_handler.go | 80 +++ .../httpapi/reconcile_handler_test.go | 170 +++++ backend/internal/httpapi/router_test.go | 67 ++ backend/internal/users/ent_repository.go | 55 ++ backend/internal/users/ent_repository_test.go | 135 ++++ .../internal/users/migration_contract_test.go | 48 ++ backend/internal/users/model.go | 18 + backend/internal/users/repository.go | 16 + backend/internal/users/service.go | 57 ++ backend/internal/users/service_test.go | 147 +++++ compose.yaml | 4 + ...026-08-21-durable-internal-user-mapping.md | 511 +++++++++++++++ ...21-durable-internal-user-mapping-design.md | 177 +++++ frontend/README.md | 14 +- .../app/api/v1/auth/reconcile/route.test.ts | 167 +++++ .../src/app/api/v1/auth/reconcile/route.ts | 128 ++++ frontend/src/shared/api/generated/index.ts | 4 +- frontend/src/shared/api/generated/sdk.gen.ts | 13 +- .../src/shared/api/generated/types.gen.ts | 44 +- openapi/juntly-api.v1.yaml | 58 +- supabase/.gitignore | 8 + supabase/config.toml | 388 +++++++++++ .../20260821110514_create_internal_users.sql | 14 + 57 files changed, 6781 insertions(+), 13 deletions(-) create mode 100644 backend/.env.example create mode 100644 backend/cmd/api/config.go create mode 100644 backend/cmd/api/config_test.go create mode 100644 backend/ent/client.go create mode 100644 backend/ent/ent.go create mode 100644 backend/ent/entc.go create mode 100644 backend/ent/enttest/enttest.go create mode 100644 backend/ent/hook/hook.go create mode 100644 backend/ent/internaluser.go create mode 100644 backend/ent/internaluser/internaluser.go create mode 100644 backend/ent/internaluser/where.go create mode 100644 backend/ent/internaluser_create.go create mode 100644 backend/ent/internaluser_delete.go create mode 100644 backend/ent/internaluser_query.go create mode 100644 backend/ent/internaluser_update.go create mode 100644 backend/ent/migrate/migrate.go create mode 100644 backend/ent/migrate/schema.go create mode 100644 backend/ent/mutation.go create mode 100644 backend/ent/predicate/predicate.go create mode 100644 backend/ent/runtime.go create mode 100644 backend/ent/runtime/runtime.go create mode 100644 backend/ent/schema/internaluser.go create mode 100644 backend/ent/schema/internaluser_test.go create mode 100644 backend/ent/tx.go create mode 100644 backend/go.sum create mode 100644 backend/internal/authn/clerk_verifier.go create mode 100644 backend/internal/authn/clerk_verifier_test.go create mode 100644 backend/internal/authn/middleware.go create mode 100644 backend/internal/authn/middleware_test.go create mode 100644 backend/internal/authn/verifier.go create mode 100644 backend/internal/httpapi/openapi_contract_test.go create mode 100644 backend/internal/httpapi/reconcile_handler.go create mode 100644 backend/internal/httpapi/reconcile_handler_test.go create mode 100644 backend/internal/httpapi/router_test.go create mode 100644 backend/internal/users/ent_repository.go create mode 100644 backend/internal/users/ent_repository_test.go create mode 100644 backend/internal/users/migration_contract_test.go create mode 100644 backend/internal/users/model.go create mode 100644 backend/internal/users/repository.go create mode 100644 backend/internal/users/service.go create mode 100644 backend/internal/users/service_test.go create mode 100644 docs/superpowers/plans/2026-08-21-durable-internal-user-mapping.md create mode 100644 docs/superpowers/specs/2026-08-21-durable-internal-user-mapping-design.md create mode 100644 frontend/src/app/api/v1/auth/reconcile/route.test.ts create mode 100644 frontend/src/app/api/v1/auth/reconcile/route.ts create mode 100644 supabase/.gitignore create mode 100644 supabase/config.toml create mode 100644 supabase/migrations/20260821110514_create_internal_users.sql diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..178718a --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,7 @@ +# Server-only Go API configuration. Keep real values in ignored backend/.env.local or the service environment. +DATABASE_URL= +CLERK_SECRET_KEY= +# Optional: enables networkless Clerk JWT verification when configured. +CLERK_JWT_KEY= +# Comma-separated exact allowed Clerk authorized-party origins. +CLERK_AUTHORIZED_PARTIES=http://localhost:4200 diff --git a/backend/cmd/api/config.go b/backend/cmd/api/config.go new file mode 100644 index 0000000..c0c39b5 --- /dev/null +++ b/backend/cmd/api/config.go @@ -0,0 +1,33 @@ +package main + +import ( + "errors" + "strings" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/authn" +) + +var ErrInvalidRuntimeConfig = errors.New("invalid API runtime configuration") + +type runtimeConfig struct { + databaseURL string + verifier authn.Verifier +} + +func loadRuntimeConfig(lookup func(string) string) (runtimeConfig, error) { + databaseURL := strings.TrimSpace(lookup("DATABASE_URL")) + if databaseURL == "" { + return runtimeConfig{}, ErrInvalidRuntimeConfig + } + + verifier, err := authn.NewClerkVerifier(authn.ClerkVerifierConfig{ + SecretKey: lookup("CLERK_SECRET_KEY"), + JWTKey: lookup("CLERK_JWT_KEY"), + AuthorizedParties: strings.Split(lookup("CLERK_AUTHORIZED_PARTIES"), ","), + }) + if err != nil { + return runtimeConfig{}, ErrInvalidRuntimeConfig + } + + return runtimeConfig{databaseURL: databaseURL, verifier: verifier}, nil +} diff --git a/backend/cmd/api/config_test.go b/backend/cmd/api/config_test.go new file mode 100644 index 0000000..255f57e --- /dev/null +++ b/backend/cmd/api/config_test.go @@ -0,0 +1,66 @@ +package main + +import ( + "testing" +) + +func TestLoadRuntimeConfigRejectsMissingDatabaseURL(t *testing.T) { + t.Parallel() + + _, err := loadRuntimeConfig(func(key string) string { + if key == "CLERK_SECRET_KEY" { + return "synthetic-secret" + } + if key == "CLERK_AUTHORIZED_PARTIES" { + return "http://localhost:4200" + } + return "" + }) + if err == nil { + t.Fatal("error = nil, want missing database URL rejection") + } +} + +func TestLoadRuntimeConfigRejectsMissingAuthorizedParties(t *testing.T) { + t.Parallel() + + _, err := loadRuntimeConfig(func(key string) string { + switch key { + case "DATABASE_URL": + return "postgresql://synthetic" + case "CLERK_SECRET_KEY": + return "synthetic-secret" + default: + return "" + } + }) + if err == nil { + t.Fatal("error = nil, want missing authorized-party rejection") + } +} + +func TestLoadRuntimeConfigBuildsVerifierWithoutPersistingCredentials(t *testing.T) { + t.Parallel() + + config, err := loadRuntimeConfig(func(key string) string { + switch key { + case "DATABASE_URL": + return "postgresql://synthetic" + case "CLERK_SECRET_KEY": + return "synthetic-secret" + case "CLERK_AUTHORIZED_PARTIES": + return "http://localhost:4200, https://app.example.test" + default: + return "" + } + }) + if err != nil { + t.Fatalf("load runtime config: %v", err) + } + if config.databaseURL != "postgresql://synthetic" { + t.Fatalf("database URL = %q", config.databaseURL) + } + if config.verifier == nil { + t.Fatal("verifier is nil") + } +} diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index f03bcfe..c94bfc7 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -2,7 +2,9 @@ package main import ( "context" + "database/sql" "errors" + "io" "log/slog" "net/http" "os" @@ -10,8 +12,13 @@ import ( "syscall" "time" + "entgo.io/ent/dialect" + entsql "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/health" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/httpapi" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" + _ "github.com/jackc/pgx/v5/stdlib" ) var version = "0.1.0" @@ -24,15 +31,26 @@ func main() { } func run() error { + config, err := loadRuntimeConfig(os.Getenv) + if err != nil { + return err + } + handler, closer, err := newAPIHandler(config) + if err != nil { + return err + } + defer func() { + _ = closer.Close() + }() + addr := os.Getenv("JUNTLY_API_ADDR") if addr == "" { addr = ":8080" } - service := health.NewService(version, time.Now) server := &http.Server{ Addr: addr, - Handler: httpapi.NewRouter(service), + Handler: handler, ReadHeaderTimeout: 5 * time.Second, } @@ -64,3 +82,15 @@ func run() error { return err } } + +func newAPIHandler(config runtimeConfig) (http.Handler, io.Closer, error) { + database, err := sql.Open("pgx", config.databaseURL) + if err != nil { + return nil, nil, err + } + client := ent.NewClient(ent.Driver(entsql.OpenDB(dialect.Postgres, database))) + + healthService := health.NewService(version, time.Now) + userService := users.NewService(users.NewEntRepository(client)) + return httpapi.NewRouter(healthService, config.verifier, userService), client, nil +} diff --git a/backend/ent/client.go b/backend/ent/client.go new file mode 100644 index 0000000..39bc21a --- /dev/null +++ b/backend/ent/client.go @@ -0,0 +1,341 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "log" + "reflect" + + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/migrate" + "github.com/google/uuid" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" +) + +// Client is the client that holds all ent builders. +type Client struct { + config + // Schema is the client for creating, migrating and dropping schema. + Schema *migrate.Schema + // InternalUser is the client for interacting with the InternalUser builders. + InternalUser *InternalUserClient +} + +// NewClient creates a new client configured with the given options. +func NewClient(opts ...Option) *Client { + client := &Client{config: newConfig(opts...)} + client.init() + return client +} + +func (c *Client) init() { + c.Schema = migrate.NewSchema(c.driver) + c.InternalUser = NewInternalUserClient(c.config) +} + +type ( + // config is the configuration for the client and its builder. + config struct { + // driver used for executing database requests. + driver dialect.Driver + // debug enable a debug logging. + debug bool + // log used for logging on debug mode. + log func(...any) + // hooks to execute on mutations. + hooks *hooks + // interceptors to execute on queries. + inters *inters + } + // Option function to configure the client. + Option func(*config) +) + +// newConfig creates a new config for the client. +func newConfig(opts ...Option) config { + cfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}} + cfg.options(opts...) + return cfg +} + +// options applies the options on the config object. +func (c *config) options(opts ...Option) { + for _, opt := range opts { + opt(c) + } + if c.debug { + c.driver = dialect.Debug(c.driver, c.log) + } +} + +// Debug enables debug logging on the ent.Driver. +func Debug() Option { + return func(c *config) { + c.debug = true + } +} + +// Log sets the logging function for debug mode. +func Log(fn func(...any)) Option { + return func(c *config) { + c.log = fn + } +} + +// Driver configures the client driver. +func Driver(driver dialect.Driver) Option { + return func(c *config) { + c.driver = driver + } +} + +// Open opens a database/sql.DB specified by the driver name and +// the data source name, and returns a new client attached to it. +// Optional parameters can be added for configuring the client. +func Open(driverName, dataSourceName string, options ...Option) (*Client, error) { + switch driverName { + case dialect.MySQL, dialect.Postgres, dialect.SQLite: + drv, err := sql.Open(driverName, dataSourceName) + if err != nil { + return nil, err + } + return NewClient(append(options, Driver(drv))...), nil + default: + return nil, fmt.Errorf("unsupported driver: %q", driverName) + } +} + +// ErrTxStarted is returned when trying to start a new transaction from a transactional client. +var ErrTxStarted = errors.New("ent: cannot start a transaction within a transaction") + +// Tx returns a new transactional client. The provided context +// is used until the transaction is committed or rolled back. +func (c *Client) Tx(ctx context.Context) (*Tx, error) { + if _, ok := c.driver.(*txDriver); ok { + return nil, ErrTxStarted + } + tx, err := newTx(ctx, c.driver) + if err != nil { + return nil, fmt.Errorf("ent: starting a transaction: %w", err) + } + cfg := c.config + cfg.driver = tx + return &Tx{ + ctx: ctx, + config: cfg, + InternalUser: NewInternalUserClient(cfg), + }, nil +} + +// BeginTx returns a transactional client with specified options. +func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + if _, ok := c.driver.(*txDriver); ok { + return nil, errors.New("ent: cannot start a transaction within a transaction") + } + tx, err := c.driver.(interface { + BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error) + }).BeginTx(ctx, opts) + if err != nil { + return nil, fmt.Errorf("ent: starting a transaction: %w", err) + } + cfg := c.config + cfg.driver = &txDriver{tx: tx, drv: c.driver} + return &Tx{ + ctx: ctx, + config: cfg, + InternalUser: NewInternalUserClient(cfg), + }, nil +} + +// Debug returns a new debug-client. It's used to get verbose logging on specific operations. +// +// client.Debug(). +// InternalUser. +// Query(). +// Count(ctx) +func (c *Client) Debug() *Client { + if c.debug { + return c + } + cfg := c.config + cfg.driver = dialect.Debug(c.driver, c.log) + client := &Client{config: cfg} + client.init() + return client +} + +// Close closes the database connection and prevents new queries from starting. +func (c *Client) Close() error { + return c.driver.Close() +} + +// Use adds the mutation hooks to all the entity clients. +// In order to add hooks to a specific client, call: `client.Node.Use(...)`. +func (c *Client) Use(hooks ...Hook) { + c.InternalUser.Use(hooks...) +} + +// Intercept adds the query interceptors to all the entity clients. +// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. +func (c *Client) Intercept(interceptors ...Interceptor) { + c.InternalUser.Intercept(interceptors...) +} + +// Mutate implements the ent.Mutator interface. +func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { + switch m := m.(type) { + case *InternalUserMutation: + return c.InternalUser.mutate(ctx, m) + default: + return nil, fmt.Errorf("ent: unknown mutation type %T", m) + } +} + +// InternalUserClient is a client for the InternalUser schema. +type InternalUserClient struct { + config +} + +// NewInternalUserClient returns a client for the InternalUser from the given config. +func NewInternalUserClient(c config) *InternalUserClient { + return &InternalUserClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `internaluser.Hooks(f(g(h())))`. +func (c *InternalUserClient) Use(hooks ...Hook) { + c.hooks.InternalUser = append(c.hooks.InternalUser, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `internaluser.Intercept(f(g(h())))`. +func (c *InternalUserClient) Intercept(interceptors ...Interceptor) { + c.inters.InternalUser = append(c.inters.InternalUser, interceptors...) +} + +// Create returns a builder for creating a InternalUser entity. +func (c *InternalUserClient) Create() *InternalUserCreate { + mutation := newInternalUserMutation(c.config, OpCreate) + return &InternalUserCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of InternalUser entities. +func (c *InternalUserClient) CreateBulk(builders ...*InternalUserCreate) *InternalUserCreateBulk { + return &InternalUserCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *InternalUserClient) MapCreateBulk(slice any, setFunc func(*InternalUserCreate, int)) *InternalUserCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &InternalUserCreateBulk{err: fmt.Errorf("calling to InternalUserClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*InternalUserCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &InternalUserCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for InternalUser. +func (c *InternalUserClient) Update() *InternalUserUpdate { + mutation := newInternalUserMutation(c.config, OpUpdate) + return &InternalUserUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *InternalUserClient) UpdateOne(_m *InternalUser) *InternalUserUpdateOne { + mutation := newInternalUserMutation(c.config, OpUpdateOne, withInternalUser(_m)) + return &InternalUserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *InternalUserClient) UpdateOneID(id uuid.UUID) *InternalUserUpdateOne { + mutation := newInternalUserMutation(c.config, OpUpdateOne, withInternalUserID(id)) + return &InternalUserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for InternalUser. +func (c *InternalUserClient) Delete() *InternalUserDelete { + mutation := newInternalUserMutation(c.config, OpDelete) + return &InternalUserDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *InternalUserClient) DeleteOne(_m *InternalUser) *InternalUserDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *InternalUserClient) DeleteOneID(id uuid.UUID) *InternalUserDeleteOne { + builder := c.Delete().Where(internaluser.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &InternalUserDeleteOne{builder} +} + +// Query returns a query builder for InternalUser. +func (c *InternalUserClient) Query() *InternalUserQuery { + return &InternalUserQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeInternalUser}, + inters: c.Interceptors(), + } +} + +// Get returns a InternalUser entity by its id. +func (c *InternalUserClient) Get(ctx context.Context, id uuid.UUID) (*InternalUser, error) { + return c.Query().Where(internaluser.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *InternalUserClient) GetX(ctx context.Context, id uuid.UUID) *InternalUser { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *InternalUserClient) Hooks() []Hook { + return c.hooks.InternalUser +} + +// Interceptors returns the client interceptors. +func (c *InternalUserClient) Interceptors() []Interceptor { + return c.inters.InternalUser +} + +func (c *InternalUserClient) mutate(ctx context.Context, m *InternalUserMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&InternalUserCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&InternalUserUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&InternalUserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&InternalUserDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown InternalUser mutation op: %q", m.Op()) + } +} + +// hooks and interceptors per client, for fast access. +type ( + hooks struct { + InternalUser []ent.Hook + } + inters struct { + InternalUser []ent.Interceptor + } +) diff --git a/backend/ent/ent.go b/backend/ent/ent.go new file mode 100644 index 0000000..5e31ef1 --- /dev/null +++ b/backend/ent/ent.go @@ -0,0 +1,608 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "reflect" + "sync" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" +) + +// ent aliases to avoid import conflicts in user's code. +type ( + Op = ent.Op + Hook = ent.Hook + Value = ent.Value + Query = ent.Query + QueryContext = ent.QueryContext + Querier = ent.Querier + QuerierFunc = ent.QuerierFunc + Interceptor = ent.Interceptor + InterceptFunc = ent.InterceptFunc + Traverser = ent.Traverser + TraverseFunc = ent.TraverseFunc + Policy = ent.Policy + Mutator = ent.Mutator + Mutation = ent.Mutation + MutateFunc = ent.MutateFunc +) + +type clientCtxKey struct{} + +// FromContext returns a Client stored inside a context, or nil if there isn't one. +func FromContext(ctx context.Context) *Client { + c, _ := ctx.Value(clientCtxKey{}).(*Client) + return c +} + +// NewContext returns a new context with the given Client attached. +func NewContext(parent context.Context, c *Client) context.Context { + return context.WithValue(parent, clientCtxKey{}, c) +} + +type txCtxKey struct{} + +// TxFromContext returns a Tx stored inside a context, or nil if there isn't one. +func TxFromContext(ctx context.Context) *Tx { + tx, _ := ctx.Value(txCtxKey{}).(*Tx) + return tx +} + +// NewTxContext returns a new context with the given Tx attached. +func NewTxContext(parent context.Context, tx *Tx) context.Context { + return context.WithValue(parent, txCtxKey{}, tx) +} + +// OrderFunc applies an ordering on the sql selector. +// Deprecated: Use Asc/Desc functions or the package builders instead. +type OrderFunc func(*sql.Selector) + +var ( + initCheck sync.Once + columnCheck sql.ColumnCheck +) + +// checkColumn checks if the column exists in the given table. +func checkColumn(t, c string) error { + initCheck.Do(func() { + columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ + internaluser.Table: internaluser.ValidColumn, + }) + }) + return columnCheck(t, c) +} + +// Asc applies the given fields in ASC order. +func Asc(fields ...string) func(*sql.Selector) { + return func(s *sql.Selector) { + for _, f := range fields { + if err := checkColumn(s.TableName(), f); err != nil { + s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)}) + } + s.OrderBy(sql.Asc(s.C(f))) + } + } +} + +// Desc applies the given fields in DESC order. +func Desc(fields ...string) func(*sql.Selector) { + return func(s *sql.Selector) { + for _, f := range fields { + if err := checkColumn(s.TableName(), f); err != nil { + s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)}) + } + s.OrderBy(sql.Desc(s.C(f))) + } + } +} + +// AggregateFunc applies an aggregation step on the group-by traversal/selector. +type AggregateFunc func(*sql.Selector) string + +// As is a pseudo aggregation function for renaming another other functions with custom names. For example: +// +// GroupBy(field1, field2). +// Aggregate(ent.As(ent.Sum(field1), "sum_field1"), (ent.As(ent.Sum(field2), "sum_field2")). +// Scan(ctx, &v) +func As(fn AggregateFunc, end string) AggregateFunc { + return func(s *sql.Selector) string { + return sql.As(fn(s), end) + } +} + +// Count applies the "count" aggregation function on each group. +func Count() AggregateFunc { + return func(s *sql.Selector) string { + return sql.Count("*") + } +} + +// Max applies the "max" aggregation function on the given field of each group. +func Max(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) + return "" + } + return sql.Max(s.C(field)) + } +} + +// Mean applies the "mean" aggregation function on the given field of each group. +func Mean(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) + return "" + } + return sql.Avg(s.C(field)) + } +} + +// Min applies the "min" aggregation function on the given field of each group. +func Min(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) + return "" + } + return sql.Min(s.C(field)) + } +} + +// Sum applies the "sum" aggregation function on the given field of each group. +func Sum(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) + return "" + } + return sql.Sum(s.C(field)) + } +} + +// ValidationError returns when validating a field or edge fails. +type ValidationError struct { + Name string // Field or edge name. + err error +} + +// Error implements the error interface. +func (e *ValidationError) Error() string { + return e.err.Error() +} + +// Unwrap implements the errors.Wrapper interface. +func (e *ValidationError) Unwrap() error { + return e.err +} + +// IsValidationError returns a boolean indicating whether the error is a validation error. +func IsValidationError(err error) bool { + if err == nil { + return false + } + var e *ValidationError + return errors.As(err, &e) +} + +// NotFoundError returns when trying to fetch a specific entity and it was not found in the database. +type NotFoundError struct { + label string +} + +// Error implements the error interface. +func (e *NotFoundError) Error() string { + return "ent: " + e.label + " not found" +} + +// IsNotFound returns a boolean indicating whether the error is a not found error. +func IsNotFound(err error) bool { + if err == nil { + return false + } + var e *NotFoundError + return errors.As(err, &e) +} + +// MaskNotFound masks not found error. +func MaskNotFound(err error) error { + if IsNotFound(err) { + return nil + } + return err +} + +// NotSingularError returns when trying to fetch a singular entity and more then one was found in the database. +type NotSingularError struct { + label string +} + +// Error implements the error interface. +func (e *NotSingularError) Error() string { + return "ent: " + e.label + " not singular" +} + +// IsNotSingular returns a boolean indicating whether the error is a not singular error. +func IsNotSingular(err error) bool { + if err == nil { + return false + } + var e *NotSingularError + return errors.As(err, &e) +} + +// NotLoadedError returns when trying to get a node that was not loaded by the query. +type NotLoadedError struct { + edge string +} + +// Error implements the error interface. +func (e *NotLoadedError) Error() string { + return "ent: " + e.edge + " edge was not loaded" +} + +// IsNotLoaded returns a boolean indicating whether the error is a not loaded error. +func IsNotLoaded(err error) bool { + if err == nil { + return false + } + var e *NotLoadedError + return errors.As(err, &e) +} + +// ConstraintError returns when trying to create/update one or more entities and +// one or more of their constraints failed. For example, violation of edge or +// field uniqueness. +type ConstraintError struct { + msg string + wrap error +} + +// Error implements the error interface. +func (e ConstraintError) Error() string { + return "ent: constraint failed: " + e.msg +} + +// Unwrap implements the errors.Wrapper interface. +func (e *ConstraintError) Unwrap() error { + return e.wrap +} + +// IsConstraintError returns a boolean indicating whether the error is a constraint failure. +func IsConstraintError(err error) bool { + if err == nil { + return false + } + var e *ConstraintError + return errors.As(err, &e) +} + +// selector embedded by the different Select/GroupBy builders. +type selector struct { + label string + flds *[]string + fns []AggregateFunc + scan func(context.Context, any) error +} + +// ScanX is like Scan, but panics if an error occurs. +func (s *selector) ScanX(ctx context.Context, v any) { + if err := s.scan(ctx, v); err != nil { + panic(err) + } +} + +// Strings returns list of strings from a selector. It is only allowed when selecting one field. +func (s *selector) Strings(ctx context.Context) ([]string, error) { + if len(*s.flds) > 1 { + return nil, errors.New("ent: Strings is not achievable when selecting more than 1 field") + } + var v []string + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// StringsX is like Strings, but panics if an error occurs. +func (s *selector) StringsX(ctx context.Context) []string { + v, err := s.Strings(ctx) + if err != nil { + panic(err) + } + return v +} + +// String returns a single string from a selector. It is only allowed when selecting one field. +func (s *selector) String(ctx context.Context) (_ string, err error) { + var v []string + if v, err = s.Strings(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("ent: Strings returned %d results when one was expected", len(v)) + } + return +} + +// StringX is like String, but panics if an error occurs. +func (s *selector) StringX(ctx context.Context) string { + v, err := s.String(ctx) + if err != nil { + panic(err) + } + return v +} + +// Ints returns list of ints from a selector. It is only allowed when selecting one field. +func (s *selector) Ints(ctx context.Context) ([]int, error) { + if len(*s.flds) > 1 { + return nil, errors.New("ent: Ints is not achievable when selecting more than 1 field") + } + var v []int + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// IntsX is like Ints, but panics if an error occurs. +func (s *selector) IntsX(ctx context.Context) []int { + v, err := s.Ints(ctx) + if err != nil { + panic(err) + } + return v +} + +// Int returns a single int from a selector. It is only allowed when selecting one field. +func (s *selector) Int(ctx context.Context) (_ int, err error) { + var v []int + if v, err = s.Ints(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("ent: Ints returned %d results when one was expected", len(v)) + } + return +} + +// IntX is like Int, but panics if an error occurs. +func (s *selector) IntX(ctx context.Context) int { + v, err := s.Int(ctx) + if err != nil { + panic(err) + } + return v +} + +// Float64s returns list of float64s from a selector. It is only allowed when selecting one field. +func (s *selector) Float64s(ctx context.Context) ([]float64, error) { + if len(*s.flds) > 1 { + return nil, errors.New("ent: Float64s is not achievable when selecting more than 1 field") + } + var v []float64 + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// Float64sX is like Float64s, but panics if an error occurs. +func (s *selector) Float64sX(ctx context.Context) []float64 { + v, err := s.Float64s(ctx) + if err != nil { + panic(err) + } + return v +} + +// Float64 returns a single float64 from a selector. It is only allowed when selecting one field. +func (s *selector) Float64(ctx context.Context) (_ float64, err error) { + var v []float64 + if v, err = s.Float64s(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("ent: Float64s returned %d results when one was expected", len(v)) + } + return +} + +// Float64X is like Float64, but panics if an error occurs. +func (s *selector) Float64X(ctx context.Context) float64 { + v, err := s.Float64(ctx) + if err != nil { + panic(err) + } + return v +} + +// Bools returns list of bools from a selector. It is only allowed when selecting one field. +func (s *selector) Bools(ctx context.Context) ([]bool, error) { + if len(*s.flds) > 1 { + return nil, errors.New("ent: Bools is not achievable when selecting more than 1 field") + } + var v []bool + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// BoolsX is like Bools, but panics if an error occurs. +func (s *selector) BoolsX(ctx context.Context) []bool { + v, err := s.Bools(ctx) + if err != nil { + panic(err) + } + return v +} + +// Bool returns a single bool from a selector. It is only allowed when selecting one field. +func (s *selector) Bool(ctx context.Context) (_ bool, err error) { + var v []bool + if v, err = s.Bools(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("ent: Bools returned %d results when one was expected", len(v)) + } + return +} + +// BoolX is like Bool, but panics if an error occurs. +func (s *selector) BoolX(ctx context.Context) bool { + v, err := s.Bool(ctx) + if err != nil { + panic(err) + } + return v +} + +// withHooks invokes the builder operation with the given hooks, if any. +func withHooks[V Value, M any, PM interface { + *M + Mutation +}](ctx context.Context, exec func(context.Context) (V, error), mutation PM, hooks []Hook) (value V, err error) { + if len(hooks) == 0 { + return exec(ctx) + } + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutationT, ok := any(m).(PM) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + // Set the mutation to the builder. + *mutation = *mutationT + return exec(ctx) + }) + for i := len(hooks) - 1; i >= 0; i-- { + if hooks[i] == nil { + return value, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = hooks[i](mut) + } + v, err := mut.Mutate(ctx, mutation) + if err != nil { + return value, err + } + nv, ok := v.(V) + if !ok { + return value, fmt.Errorf("unexpected node type %T returned from %T", v, mutation) + } + return nv, nil +} + +// setContextOp returns a new context with the given QueryContext attached (including its op) in case it does not exist. +func setContextOp(ctx context.Context, qc *QueryContext, op string) context.Context { + if ent.QueryFromContext(ctx) == nil { + qc.Op = op + ctx = ent.NewQueryContext(ctx, qc) + } + return ctx +} + +func querierAll[V Value, Q interface { + sqlAll(context.Context, ...queryHook) (V, error) +}]() Querier { + return QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + return query.sqlAll(ctx) + }) +} + +func querierCount[Q interface { + sqlCount(context.Context) (int, error) +}]() Querier { + return QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + return query.sqlCount(ctx) + }) +} + +func withInterceptors[V Value](ctx context.Context, q Query, qr Querier, inters []Interceptor) (v V, err error) { + for i := len(inters) - 1; i >= 0; i-- { + qr = inters[i].Intercept(qr) + } + rv, err := qr.Query(ctx, q) + if err != nil { + return v, err + } + vt, ok := rv.(V) + if !ok { + return v, fmt.Errorf("unexpected type %T returned from %T. expected type: %T", vt, q, v) + } + return vt, nil +} + +func scanWithInterceptors[Q1 ent.Query, Q2 interface { + sqlScan(context.Context, Q1, any) error +}](ctx context.Context, rootQuery Q1, selectOrGroup Q2, inters []Interceptor, v any) error { + rv := reflect.ValueOf(v) + var qr Querier = QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q1) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + if err := selectOrGroup.sqlScan(ctx, query, v); err != nil { + return nil, err + } + if k := rv.Kind(); k == reflect.Pointer && rv.Elem().CanInterface() { + return rv.Elem().Interface(), nil + } + return v, nil + }) + for i := len(inters) - 1; i >= 0; i-- { + qr = inters[i].Intercept(qr) + } + vv, err := qr.Query(ctx, rootQuery) + if err != nil { + return err + } + switch rv2 := reflect.ValueOf(vv); { + case rv.IsNil(), rv2.IsNil(), rv.Kind() != reflect.Pointer: + case rv.Type() == rv2.Type(): + rv.Elem().Set(rv2.Elem()) + case rv.Elem().Type() == rv2.Type(): + rv.Elem().Set(rv2) + } + return nil +} + +// queryHook describes an internal hook for the different sqlAll methods. +type queryHook func(context.Context, *sqlgraph.QuerySpec) diff --git a/backend/ent/entc.go b/backend/ent/entc.go new file mode 100644 index 0000000..8d3fdfd --- /dev/null +++ b/backend/ent/entc.go @@ -0,0 +1,3 @@ +package ent + +//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema diff --git a/backend/ent/enttest/enttest.go b/backend/ent/enttest/enttest.go new file mode 100644 index 0000000..cdd2cb5 --- /dev/null +++ b/backend/ent/enttest/enttest.go @@ -0,0 +1,84 @@ +// Code generated by ent, DO NOT EDIT. + +package enttest + +import ( + "context" + + "github.com/SourceSenseiTheRealOne/juntly/backend/ent" + // required by schema hooks. + _ "github.com/SourceSenseiTheRealOne/juntly/backend/ent/runtime" + + "entgo.io/ent/dialect/sql/schema" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/migrate" +) + +type ( + // TestingT is the interface that is shared between + // testing.T and testing.B and used by enttest. + TestingT interface { + FailNow() + Error(...any) + } + + // Option configures client creation. + Option func(*options) + + options struct { + opts []ent.Option + migrateOpts []schema.MigrateOption + } +) + +// WithOptions forwards options to client creation. +func WithOptions(opts ...ent.Option) Option { + return func(o *options) { + o.opts = append(o.opts, opts...) + } +} + +// WithMigrateOptions forwards options to auto migration. +func WithMigrateOptions(opts ...schema.MigrateOption) Option { + return func(o *options) { + o.migrateOpts = append(o.migrateOpts, opts...) + } +} + +func newOptions(opts []Option) *options { + o := &options{} + for _, opt := range opts { + opt(o) + } + return o +} + +// Open calls ent.Open and auto-run migration. +func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Client { + o := newOptions(opts) + c, err := ent.Open(driverName, dataSourceName, o.opts...) + if err != nil { + t.Error(err) + t.FailNow() + } + migrateSchema(t, c, o) + return c +} + +// NewClient calls ent.NewClient and auto-run migration. +func NewClient(t TestingT, opts ...Option) *ent.Client { + o := newOptions(opts) + c := ent.NewClient(o.opts...) + migrateSchema(t, c, o) + return c +} +func migrateSchema(t TestingT, c *ent.Client, o *options) { + tables, err := schema.CopyTables(migrate.Tables) + if err != nil { + t.Error(err) + t.FailNow() + } + if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil { + t.Error(err) + t.FailNow() + } +} diff --git a/backend/ent/hook/hook.go b/backend/ent/hook/hook.go new file mode 100644 index 0000000..89bff2d --- /dev/null +++ b/backend/ent/hook/hook.go @@ -0,0 +1,199 @@ +// Code generated by ent, DO NOT EDIT. + +package hook + +import ( + "context" + "fmt" + + "github.com/SourceSenseiTheRealOne/juntly/backend/ent" +) + +// The InternalUserFunc type is an adapter to allow the use of ordinary +// function as InternalUser mutator. +type InternalUserFunc func(context.Context, *ent.InternalUserMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f InternalUserFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.InternalUserMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.InternalUserMutation", m) +} + +// Condition is a hook condition function. +type Condition func(context.Context, ent.Mutation) bool + +// And groups conditions with the AND operator. +func And(first, second Condition, rest ...Condition) Condition { + return func(ctx context.Context, m ent.Mutation) bool { + if !first(ctx, m) || !second(ctx, m) { + return false + } + for _, cond := range rest { + if !cond(ctx, m) { + return false + } + } + return true + } +} + +// Or groups conditions with the OR operator. +func Or(first, second Condition, rest ...Condition) Condition { + return func(ctx context.Context, m ent.Mutation) bool { + if first(ctx, m) || second(ctx, m) { + return true + } + for _, cond := range rest { + if cond(ctx, m) { + return true + } + } + return false + } +} + +// Not negates a given condition. +func Not(cond Condition) Condition { + return func(ctx context.Context, m ent.Mutation) bool { + return !cond(ctx, m) + } +} + +// HasOp is a condition testing mutation operation. +func HasOp(op ent.Op) Condition { + return func(_ context.Context, m ent.Mutation) bool { + return m.Op().Is(op) + } +} + +// HasAddedFields is a condition validating `.AddedField` on fields. +func HasAddedFields(field string, fields ...string) Condition { + return func(_ context.Context, m ent.Mutation) bool { + if _, exists := m.AddedField(field); !exists { + return false + } + for _, field := range fields { + if _, exists := m.AddedField(field); !exists { + return false + } + } + return true + } +} + +// HasClearedFields is a condition validating `.FieldCleared` on fields. +func HasClearedFields(field string, fields ...string) Condition { + return func(_ context.Context, m ent.Mutation) bool { + if exists := m.FieldCleared(field); !exists { + return false + } + for _, field := range fields { + if exists := m.FieldCleared(field); !exists { + return false + } + } + return true + } +} + +// HasFields is a condition validating `.Field` on fields. +func HasFields(field string, fields ...string) Condition { + return func(_ context.Context, m ent.Mutation) bool { + if _, exists := m.Field(field); !exists { + return false + } + for _, field := range fields { + if _, exists := m.Field(field); !exists { + return false + } + } + return true + } +} + +// If executes the given hook under condition. +// +// hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...))) +func If(hk ent.Hook, cond Condition) ent.Hook { + return func(next ent.Mutator) ent.Mutator { + return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if cond(ctx, m) { + return hk(next).Mutate(ctx, m) + } + return next.Mutate(ctx, m) + }) + } +} + +// On executes the given hook only for the given operation. +// +// hook.On(Log, ent.Delete|ent.Create) +func On(hk ent.Hook, op ent.Op) ent.Hook { + return If(hk, HasOp(op)) +} + +// Unless skips the given hook only for the given operation. +// +// hook.Unless(Log, ent.Update|ent.UpdateOne) +func Unless(hk ent.Hook, op ent.Op) ent.Hook { + return If(hk, Not(HasOp(op))) +} + +// FixedError is a hook returning a fixed error. +func FixedError(err error) ent.Hook { + return func(ent.Mutator) ent.Mutator { + return ent.MutateFunc(func(context.Context, ent.Mutation) (ent.Value, error) { + return nil, err + }) + } +} + +// Reject returns a hook that rejects all operations that match op. +// +// func (T) Hooks() []ent.Hook { +// return []ent.Hook{ +// Reject(ent.Delete|ent.Update), +// } +// } +func Reject(op ent.Op) ent.Hook { + hk := FixedError(fmt.Errorf("%s operation is not allowed", op)) + return On(hk, op) +} + +// Chain acts as a list of hooks and is effectively immutable. +// Once created, it will always hold the same set of hooks in the same order. +type Chain struct { + hooks []ent.Hook +} + +// NewChain creates a new chain of hooks. +func NewChain(hooks ...ent.Hook) Chain { + return Chain{append([]ent.Hook(nil), hooks...)} +} + +// Hook chains the list of hooks and returns the final hook. +func (c Chain) Hook() ent.Hook { + return func(mutator ent.Mutator) ent.Mutator { + for i := len(c.hooks) - 1; i >= 0; i-- { + mutator = c.hooks[i](mutator) + } + return mutator + } +} + +// Append extends a chain, adding the specified hook +// as the last ones in the mutation flow. +func (c Chain) Append(hooks ...ent.Hook) Chain { + newHooks := make([]ent.Hook, 0, len(c.hooks)+len(hooks)) + newHooks = append(newHooks, c.hooks...) + newHooks = append(newHooks, hooks...) + return Chain{newHooks} +} + +// Extend extends a chain, adding the specified chain +// as the last ones in the mutation flow. +func (c Chain) Extend(chain Chain) Chain { + return c.Append(chain.hooks...) +} diff --git a/backend/ent/internaluser.go b/backend/ent/internaluser.go new file mode 100644 index 0000000..6514de3 --- /dev/null +++ b/backend/ent/internaluser.go @@ -0,0 +1,129 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/google/uuid" +) + +// InternalUser is the model entity for the InternalUser schema. +type InternalUser struct { + config `json:"-"` + // ID of the ent. + ID uuid.UUID `json:"id,omitempty"` + // ClerkSubject holds the value of the "clerk_subject" field. + ClerkSubject string `json:"clerk_subject,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*InternalUser) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case internaluser.FieldClerkSubject: + values[i] = new(sql.NullString) + case internaluser.FieldCreatedAt, internaluser.FieldUpdatedAt: + values[i] = new(sql.NullTime) + case internaluser.FieldID: + values[i] = new(uuid.UUID) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the InternalUser fields. +func (_m *InternalUser) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case internaluser.FieldID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value != nil { + _m.ID = *value + } + case internaluser.FieldClerkSubject: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field clerk_subject", values[i]) + } else if value.Valid { + _m.ClerkSubject = value.String + } + case internaluser.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + _m.CreatedAt = value.Time + } + case internaluser.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + _m.UpdatedAt = value.Time + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the InternalUser. +// This includes values selected through modifiers, order, etc. +func (_m *InternalUser) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// Update returns a builder for updating this InternalUser. +// Note that you need to call InternalUser.Unwrap() before calling this method if this InternalUser +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *InternalUser) Update() *InternalUserUpdateOne { + return NewInternalUserClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the InternalUser entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *InternalUser) Unwrap() *InternalUser { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: InternalUser is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *InternalUser) String() string { + var builder strings.Builder + builder.WriteString("InternalUser(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("clerk_subject=") + builder.WriteString(_m.ClerkSubject) + builder.WriteString(", ") + builder.WriteString("created_at=") + builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(_m.UpdatedAt.Format(time.ANSIC)) + builder.WriteByte(')') + return builder.String() +} + +// InternalUsers is a parsable slice of InternalUser. +type InternalUsers []*InternalUser diff --git a/backend/ent/internaluser/internaluser.go b/backend/ent/internaluser/internaluser.go new file mode 100644 index 0000000..57e0048 --- /dev/null +++ b/backend/ent/internaluser/internaluser.go @@ -0,0 +1,79 @@ +// Code generated by ent, DO NOT EDIT. + +package internaluser + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "github.com/google/uuid" +) + +const ( + // Label holds the string label denoting the internaluser type in the database. + Label = "internal_user" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldClerkSubject holds the string denoting the clerk_subject field in the database. + FieldClerkSubject = "clerk_subject" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // Table holds the table name of the internaluser in the database. + Table = "internal_users" +) + +// Columns holds all SQL columns for internaluser fields. +var Columns = []string{ + FieldID, + FieldClerkSubject, + FieldCreatedAt, + FieldUpdatedAt, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // ClerkSubjectValidator is a validator for the "clerk_subject" field. It is called by the builders before save. + ClerkSubjectValidator func(string) error + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() uuid.UUID +) + +// OrderOption defines the ordering options for the InternalUser queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByClerkSubject orders the results by the clerk_subject field. +func ByClerkSubject(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldClerkSubject, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} diff --git a/backend/ent/internaluser/where.go b/backend/ent/internaluser/where.go new file mode 100644 index 0000000..83b9f08 --- /dev/null +++ b/backend/ent/internaluser/where.go @@ -0,0 +1,231 @@ +// Code generated by ent, DO NOT EDIT. + +package internaluser + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +// ID filters vertices based on their ID field. +func ID(id uuid.UUID) predicate.InternalUser { + return predicate.InternalUser(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id uuid.UUID) predicate.InternalUser { + return predicate.InternalUser(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id uuid.UUID) predicate.InternalUser { + return predicate.InternalUser(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...uuid.UUID) predicate.InternalUser { + return predicate.InternalUser(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...uuid.UUID) predicate.InternalUser { + return predicate.InternalUser(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id uuid.UUID) predicate.InternalUser { + return predicate.InternalUser(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id uuid.UUID) predicate.InternalUser { + return predicate.InternalUser(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id uuid.UUID) predicate.InternalUser { + return predicate.InternalUser(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id uuid.UUID) predicate.InternalUser { + return predicate.InternalUser(sql.FieldLTE(FieldID, id)) +} + +// ClerkSubject applies equality check predicate on the "clerk_subject" field. It's identical to ClerkSubjectEQ. +func ClerkSubject(v string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldEQ(FieldClerkSubject, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// ClerkSubjectEQ applies the EQ predicate on the "clerk_subject" field. +func ClerkSubjectEQ(v string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldEQ(FieldClerkSubject, v)) +} + +// ClerkSubjectNEQ applies the NEQ predicate on the "clerk_subject" field. +func ClerkSubjectNEQ(v string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldNEQ(FieldClerkSubject, v)) +} + +// ClerkSubjectIn applies the In predicate on the "clerk_subject" field. +func ClerkSubjectIn(vs ...string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldIn(FieldClerkSubject, vs...)) +} + +// ClerkSubjectNotIn applies the NotIn predicate on the "clerk_subject" field. +func ClerkSubjectNotIn(vs ...string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldNotIn(FieldClerkSubject, vs...)) +} + +// ClerkSubjectGT applies the GT predicate on the "clerk_subject" field. +func ClerkSubjectGT(v string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldGT(FieldClerkSubject, v)) +} + +// ClerkSubjectGTE applies the GTE predicate on the "clerk_subject" field. +func ClerkSubjectGTE(v string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldGTE(FieldClerkSubject, v)) +} + +// ClerkSubjectLT applies the LT predicate on the "clerk_subject" field. +func ClerkSubjectLT(v string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldLT(FieldClerkSubject, v)) +} + +// ClerkSubjectLTE applies the LTE predicate on the "clerk_subject" field. +func ClerkSubjectLTE(v string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldLTE(FieldClerkSubject, v)) +} + +// ClerkSubjectContains applies the Contains predicate on the "clerk_subject" field. +func ClerkSubjectContains(v string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldContains(FieldClerkSubject, v)) +} + +// ClerkSubjectHasPrefix applies the HasPrefix predicate on the "clerk_subject" field. +func ClerkSubjectHasPrefix(v string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldHasPrefix(FieldClerkSubject, v)) +} + +// ClerkSubjectHasSuffix applies the HasSuffix predicate on the "clerk_subject" field. +func ClerkSubjectHasSuffix(v string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldHasSuffix(FieldClerkSubject, v)) +} + +// ClerkSubjectEqualFold applies the EqualFold predicate on the "clerk_subject" field. +func ClerkSubjectEqualFold(v string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldEqualFold(FieldClerkSubject, v)) +} + +// ClerkSubjectContainsFold applies the ContainsFold predicate on the "clerk_subject" field. +func ClerkSubjectContainsFold(v string) predicate.InternalUser { + return predicate.InternalUser(sql.FieldContainsFold(FieldClerkSubject, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.InternalUser { + return predicate.InternalUser(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.InternalUser) predicate.InternalUser { + return predicate.InternalUser(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.InternalUser) predicate.InternalUser { + return predicate.InternalUser(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.InternalUser) predicate.InternalUser { + return predicate.InternalUser(sql.NotPredicates(p)) +} diff --git a/backend/ent/internaluser_create.go b/backend/ent/internaluser_create.go new file mode 100644 index 0000000..9a6038b --- /dev/null +++ b/backend/ent/internaluser_create.go @@ -0,0 +1,269 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/google/uuid" +) + +// InternalUserCreate is the builder for creating a InternalUser entity. +type InternalUserCreate struct { + config + mutation *InternalUserMutation + hooks []Hook +} + +// SetClerkSubject sets the "clerk_subject" field. +func (_c *InternalUserCreate) SetClerkSubject(v string) *InternalUserCreate { + _c.mutation.SetClerkSubject(v) + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *InternalUserCreate) SetCreatedAt(v time.Time) *InternalUserCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *InternalUserCreate) SetNillableCreatedAt(v *time.Time) *InternalUserCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *InternalUserCreate) SetUpdatedAt(v time.Time) *InternalUserCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (_c *InternalUserCreate) SetNillableUpdatedAt(v *time.Time) *InternalUserCreate { + if v != nil { + _c.SetUpdatedAt(*v) + } + return _c +} + +// SetID sets the "id" field. +func (_c *InternalUserCreate) SetID(v uuid.UUID) *InternalUserCreate { + _c.mutation.SetID(v) + return _c +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (_c *InternalUserCreate) SetNillableID(v *uuid.UUID) *InternalUserCreate { + if v != nil { + _c.SetID(*v) + } + return _c +} + +// Mutation returns the InternalUserMutation object of the builder. +func (_c *InternalUserCreate) Mutation() *InternalUserMutation { + return _c.mutation +} + +// Save creates the InternalUser in the database. +func (_c *InternalUserCreate) Save(ctx context.Context) (*InternalUser, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *InternalUserCreate) SaveX(ctx context.Context) *InternalUser { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *InternalUserCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *InternalUserCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *InternalUserCreate) defaults() { + if _, ok := _c.mutation.CreatedAt(); !ok { + v := internaluser.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + v := internaluser.DefaultUpdatedAt() + _c.mutation.SetUpdatedAt(v) + } + if _, ok := _c.mutation.ID(); !ok { + v := internaluser.DefaultID() + _c.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *InternalUserCreate) check() error { + if _, ok := _c.mutation.ClerkSubject(); !ok { + return &ValidationError{Name: "clerk_subject", err: errors.New(`ent: missing required field "InternalUser.clerk_subject"`)} + } + if v, ok := _c.mutation.ClerkSubject(); ok { + if err := internaluser.ClerkSubjectValidator(v); err != nil { + return &ValidationError{Name: "clerk_subject", err: fmt.Errorf(`ent: validator failed for field "InternalUser.clerk_subject": %w`, err)} + } + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "InternalUser.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "InternalUser.updated_at"`)} + } + return nil +} + +func (_c *InternalUserCreate) sqlSave(ctx context.Context) (*InternalUser, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(*uuid.UUID); ok { + _node.ID = *id + } else if err := _node.ID.Scan(_spec.ID.Value); err != nil { + return nil, err + } + } + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *InternalUserCreate) createSpec() (*InternalUser, *sqlgraph.CreateSpec) { + var ( + _node = &InternalUser{config: _c.config} + _spec = sqlgraph.NewCreateSpec(internaluser.Table, sqlgraph.NewFieldSpec(internaluser.FieldID, field.TypeUUID)) + ) + if id, ok := _c.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = &id + } + if value, ok := _c.mutation.ClerkSubject(); ok { + _spec.SetField(internaluser.FieldClerkSubject, field.TypeString, value) + _node.ClerkSubject = value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(internaluser.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(internaluser.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + return _node, _spec +} + +// InternalUserCreateBulk is the builder for creating many InternalUser entities in bulk. +type InternalUserCreateBulk struct { + config + err error + builders []*InternalUserCreate +} + +// Save creates the InternalUser entities in the database. +func (_c *InternalUserCreateBulk) Save(ctx context.Context) ([]*InternalUser, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*InternalUser, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*InternalUserMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *InternalUserCreateBulk) SaveX(ctx context.Context) []*InternalUser { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *InternalUserCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *InternalUserCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/internaluser_delete.go b/backend/ent/internaluser_delete.go new file mode 100644 index 0000000..c36f2a1 --- /dev/null +++ b/backend/ent/internaluser_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" +) + +// InternalUserDelete is the builder for deleting a InternalUser entity. +type InternalUserDelete struct { + config + hooks []Hook + mutation *InternalUserMutation +} + +// Where appends a list predicates to the InternalUserDelete builder. +func (_d *InternalUserDelete) Where(ps ...predicate.InternalUser) *InternalUserDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *InternalUserDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *InternalUserDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *InternalUserDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(internaluser.Table, sqlgraph.NewFieldSpec(internaluser.FieldID, field.TypeUUID)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// InternalUserDeleteOne is the builder for deleting a single InternalUser entity. +type InternalUserDeleteOne struct { + _d *InternalUserDelete +} + +// Where appends a list predicates to the InternalUserDelete builder. +func (_d *InternalUserDeleteOne) Where(ps ...predicate.InternalUser) *InternalUserDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *InternalUserDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{internaluser.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *InternalUserDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/internaluser_query.go b/backend/ent/internaluser_query.go new file mode 100644 index 0000000..cd7a908 --- /dev/null +++ b/backend/ent/internaluser_query.go @@ -0,0 +1,528 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +// InternalUserQuery is the builder for querying InternalUser entities. +type InternalUserQuery struct { + config + ctx *QueryContext + order []internaluser.OrderOption + inters []Interceptor + predicates []predicate.InternalUser + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the InternalUserQuery builder. +func (_q *InternalUserQuery) Where(ps ...predicate.InternalUser) *InternalUserQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *InternalUserQuery) Limit(limit int) *InternalUserQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *InternalUserQuery) Offset(offset int) *InternalUserQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *InternalUserQuery) Unique(unique bool) *InternalUserQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *InternalUserQuery) Order(o ...internaluser.OrderOption) *InternalUserQuery { + _q.order = append(_q.order, o...) + return _q +} + +// First returns the first InternalUser entity from the query. +// Returns a *NotFoundError when no InternalUser was found. +func (_q *InternalUserQuery) First(ctx context.Context) (*InternalUser, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{internaluser.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *InternalUserQuery) FirstX(ctx context.Context) *InternalUser { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first InternalUser ID from the query. +// Returns a *NotFoundError when no InternalUser ID was found. +func (_q *InternalUserQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{internaluser.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *InternalUserQuery) FirstIDX(ctx context.Context) uuid.UUID { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single InternalUser entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one InternalUser entity is found. +// Returns a *NotFoundError when no InternalUser entities are found. +func (_q *InternalUserQuery) Only(ctx context.Context) (*InternalUser, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{internaluser.Label} + default: + return nil, &NotSingularError{internaluser.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *InternalUserQuery) OnlyX(ctx context.Context) *InternalUser { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only InternalUser ID in the query. +// Returns a *NotSingularError when more than one InternalUser ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *InternalUserQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{internaluser.Label} + default: + err = &NotSingularError{internaluser.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *InternalUserQuery) OnlyIDX(ctx context.Context) uuid.UUID { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of InternalUsers. +func (_q *InternalUserQuery) All(ctx context.Context) ([]*InternalUser, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*InternalUser, *InternalUserQuery]() + return withInterceptors[[]*InternalUser](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *InternalUserQuery) AllX(ctx context.Context) []*InternalUser { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of InternalUser IDs. +func (_q *InternalUserQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(internaluser.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *InternalUserQuery) IDsX(ctx context.Context) []uuid.UUID { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *InternalUserQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*InternalUserQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *InternalUserQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *InternalUserQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *InternalUserQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the InternalUserQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *InternalUserQuery) Clone() *InternalUserQuery { + if _q == nil { + return nil + } + return &InternalUserQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]internaluser.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.InternalUser{}, _q.predicates...), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// ClerkSubject string `json:"clerk_subject,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.InternalUser.Query(). +// GroupBy(internaluser.FieldClerkSubject). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *InternalUserQuery) GroupBy(field string, fields ...string) *InternalUserGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &InternalUserGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = internaluser.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// ClerkSubject string `json:"clerk_subject,omitempty"` +// } +// +// client.InternalUser.Query(). +// Select(internaluser.FieldClerkSubject). +// Scan(ctx, &v) +func (_q *InternalUserQuery) Select(fields ...string) *InternalUserSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &InternalUserSelect{InternalUserQuery: _q} + sbuild.label = internaluser.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a InternalUserSelect configured with the given aggregations. +func (_q *InternalUserQuery) Aggregate(fns ...AggregateFunc) *InternalUserSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *InternalUserQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !internaluser.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *InternalUserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*InternalUser, error) { + var ( + nodes = []*InternalUser{} + _spec = _q.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*InternalUser).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &InternalUser{config: _q.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (_q *InternalUserQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *InternalUserQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(internaluser.Table, internaluser.Columns, sqlgraph.NewFieldSpec(internaluser.FieldID, field.TypeUUID)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, internaluser.FieldID) + for i := range fields { + if fields[i] != internaluser.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *InternalUserQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(internaluser.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = internaluser.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// InternalUserGroupBy is the group-by builder for InternalUser entities. +type InternalUserGroupBy struct { + selector + build *InternalUserQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *InternalUserGroupBy) Aggregate(fns ...AggregateFunc) *InternalUserGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *InternalUserGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*InternalUserQuery, *InternalUserGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *InternalUserGroupBy) sqlScan(ctx context.Context, root *InternalUserQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// InternalUserSelect is the builder for selecting fields of InternalUser entities. +type InternalUserSelect struct { + *InternalUserQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *InternalUserSelect) Aggregate(fns ...AggregateFunc) *InternalUserSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *InternalUserSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*InternalUserQuery, *InternalUserSelect](ctx, _s.InternalUserQuery, _s, _s.inters, v) +} + +func (_s *InternalUserSelect) sqlScan(ctx context.Context, root *InternalUserQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/internaluser_update.go b/backend/ent/internaluser_update.go new file mode 100644 index 0000000..aaa2464 --- /dev/null +++ b/backend/ent/internaluser_update.go @@ -0,0 +1,212 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" +) + +// InternalUserUpdate is the builder for updating InternalUser entities. +type InternalUserUpdate struct { + config + hooks []Hook + mutation *InternalUserMutation +} + +// Where appends a list predicates to the InternalUserUpdate builder. +func (_u *InternalUserUpdate) Where(ps ...predicate.InternalUser) *InternalUserUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *InternalUserUpdate) SetUpdatedAt(v time.Time) *InternalUserUpdate { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// Mutation returns the InternalUserMutation object of the builder. +func (_u *InternalUserUpdate) Mutation() *InternalUserMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *InternalUserUpdate) Save(ctx context.Context) (int, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *InternalUserUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *InternalUserUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *InternalUserUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *InternalUserUpdate) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := internaluser.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +func (_u *InternalUserUpdate) sqlSave(ctx context.Context) (_node int, err error) { + _spec := sqlgraph.NewUpdateSpec(internaluser.Table, internaluser.Columns, sqlgraph.NewFieldSpec(internaluser.FieldID, field.TypeUUID)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(internaluser.FieldUpdatedAt, field.TypeTime, value) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{internaluser.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// InternalUserUpdateOne is the builder for updating a single InternalUser entity. +type InternalUserUpdateOne struct { + config + fields []string + hooks []Hook + mutation *InternalUserMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *InternalUserUpdateOne) SetUpdatedAt(v time.Time) *InternalUserUpdateOne { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// Mutation returns the InternalUserMutation object of the builder. +func (_u *InternalUserUpdateOne) Mutation() *InternalUserMutation { + return _u.mutation +} + +// Where appends a list predicates to the InternalUserUpdate builder. +func (_u *InternalUserUpdateOne) Where(ps ...predicate.InternalUser) *InternalUserUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *InternalUserUpdateOne) Select(field string, fields ...string) *InternalUserUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated InternalUser entity. +func (_u *InternalUserUpdateOne) Save(ctx context.Context) (*InternalUser, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *InternalUserUpdateOne) SaveX(ctx context.Context) *InternalUser { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *InternalUserUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *InternalUserUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *InternalUserUpdateOne) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := internaluser.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +func (_u *InternalUserUpdateOne) sqlSave(ctx context.Context) (_node *InternalUser, err error) { + _spec := sqlgraph.NewUpdateSpec(internaluser.Table, internaluser.Columns, sqlgraph.NewFieldSpec(internaluser.FieldID, field.TypeUUID)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "InternalUser.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, internaluser.FieldID) + for _, f := range fields { + if !internaluser.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != internaluser.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(internaluser.FieldUpdatedAt, field.TypeTime, value) + } + _node = &InternalUser{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{internaluser.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/ent/migrate/migrate.go b/backend/ent/migrate/migrate.go new file mode 100644 index 0000000..1956a6b --- /dev/null +++ b/backend/ent/migrate/migrate.go @@ -0,0 +1,64 @@ +// Code generated by ent, DO NOT EDIT. + +package migrate + +import ( + "context" + "fmt" + "io" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql/schema" +) + +var ( + // WithGlobalUniqueID sets the universal ids options to the migration. + // If this option is enabled, ent migration will allocate a 1<<32 range + // for the ids of each entity (table). + // Note that this option cannot be applied on tables that already exist. + WithGlobalUniqueID = schema.WithGlobalUniqueID + // WithDropColumn sets the drop column option to the migration. + // If this option is enabled, ent migration will drop old columns + // that were used for both fields and edges. This defaults to false. + WithDropColumn = schema.WithDropColumn + // WithDropIndex sets the drop index option to the migration. + // If this option is enabled, ent migration will drop old indexes + // that were defined in the schema. This defaults to false. + // Note that unique constraints are defined using `UNIQUE INDEX`, + // and therefore, it's recommended to enable this option to get more + // flexibility in the schema changes. + WithDropIndex = schema.WithDropIndex + // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. + WithForeignKeys = schema.WithForeignKeys +) + +// Schema is the API for creating, migrating and dropping a schema. +type Schema struct { + drv dialect.Driver +} + +// NewSchema creates a new schema client. +func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } + +// Create creates all schema resources. +func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { + return Create(ctx, s, Tables, opts...) +} + +// Create creates all table resources using the given schema driver. +func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error { + migrate, err := schema.NewMigrate(s.drv, opts...) + if err != nil { + return fmt.Errorf("ent/migrate: %w", err) + } + return migrate.Create(ctx, tables...) +} + +// WriteTo writes the schema changes to w instead of running them against the database. +// +// if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { +// log.Fatal(err) +// } +func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { + return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...) +} diff --git a/backend/ent/migrate/schema.go b/backend/ent/migrate/schema.go new file mode 100644 index 0000000..8d6df04 --- /dev/null +++ b/backend/ent/migrate/schema.go @@ -0,0 +1,35 @@ +// Code generated by ent, DO NOT EDIT. + +package migrate + +import ( + "entgo.io/ent/dialect/entsql" + "entgo.io/ent/dialect/sql/schema" + "entgo.io/ent/schema/field" +) + +var ( + // InternalUsersColumns holds the columns for the "internal_users" table. + InternalUsersColumns = []*schema.Column{ + {Name: "id", Type: field.TypeUUID}, + {Name: "clerk_subject", Type: field.TypeString, Unique: true, Size: 255}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + } + // InternalUsersTable holds the schema information for the "internal_users" table. + InternalUsersTable = &schema.Table{ + Name: "internal_users", + Columns: InternalUsersColumns, + PrimaryKey: []*schema.Column{InternalUsersColumns[0]}, + } + // Tables holds all the tables in the schema. + Tables = []*schema.Table{ + InternalUsersTable, + } +) + +func init() { + InternalUsersTable.Annotation = &entsql.Annotation{ + Table: "internal_users", + } +} diff --git a/backend/ent/mutation.go b/backend/ent/mutation.go new file mode 100644 index 0000000..5954739 --- /dev/null +++ b/backend/ent/mutation.go @@ -0,0 +1,469 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +const ( + // Operation types. + OpCreate = ent.OpCreate + OpDelete = ent.OpDelete + OpDeleteOne = ent.OpDeleteOne + OpUpdate = ent.OpUpdate + OpUpdateOne = ent.OpUpdateOne + + // Node types. + TypeInternalUser = "InternalUser" +) + +// InternalUserMutation represents an operation that mutates the InternalUser nodes in the graph. +type InternalUserMutation struct { + config + op Op + typ string + id *uuid.UUID + clerk_subject *string + created_at *time.Time + updated_at *time.Time + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*InternalUser, error) + predicates []predicate.InternalUser +} + +var _ ent.Mutation = (*InternalUserMutation)(nil) + +// internaluserOption allows management of the mutation configuration using functional options. +type internaluserOption func(*InternalUserMutation) + +// newInternalUserMutation creates new mutation for the InternalUser entity. +func newInternalUserMutation(c config, op Op, opts ...internaluserOption) *InternalUserMutation { + m := &InternalUserMutation{ + config: c, + op: op, + typ: TypeInternalUser, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withInternalUserID sets the ID field of the mutation. +func withInternalUserID(id uuid.UUID) internaluserOption { + return func(m *InternalUserMutation) { + var ( + err error + once sync.Once + value *InternalUser + ) + m.oldValue = func(ctx context.Context) (*InternalUser, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().InternalUser.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withInternalUser sets the old InternalUser of the mutation. +func withInternalUser(node *InternalUser) internaluserOption { + return func(m *InternalUserMutation) { + m.oldValue = func(context.Context) (*InternalUser, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m InternalUserMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m InternalUserMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of InternalUser entities. +func (m *InternalUserMutation) SetID(id uuid.UUID) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *InternalUserMutation) ID() (id uuid.UUID, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *InternalUserMutation) IDs(ctx context.Context) ([]uuid.UUID, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []uuid.UUID{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().InternalUser.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetClerkSubject sets the "clerk_subject" field. +func (m *InternalUserMutation) SetClerkSubject(s string) { + m.clerk_subject = &s +} + +// ClerkSubject returns the value of the "clerk_subject" field in the mutation. +func (m *InternalUserMutation) ClerkSubject() (r string, exists bool) { + v := m.clerk_subject + if v == nil { + return + } + return *v, true +} + +// OldClerkSubject returns the old "clerk_subject" field's value of the InternalUser entity. +// If the InternalUser object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *InternalUserMutation) OldClerkSubject(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldClerkSubject is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldClerkSubject requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldClerkSubject: %w", err) + } + return oldValue.ClerkSubject, nil +} + +// ResetClerkSubject resets all changes to the "clerk_subject" field. +func (m *InternalUserMutation) ResetClerkSubject() { + m.clerk_subject = nil +} + +// SetCreatedAt sets the "created_at" field. +func (m *InternalUserMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *InternalUserMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the InternalUser entity. +// If the InternalUser object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *InternalUserMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *InternalUserMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *InternalUserMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *InternalUserMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the InternalUser entity. +// If the InternalUser object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *InternalUserMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *InternalUserMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// Where appends a list predicates to the InternalUserMutation builder. +func (m *InternalUserMutation) Where(ps ...predicate.InternalUser) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the InternalUserMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *InternalUserMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.InternalUser, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *InternalUserMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *InternalUserMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (InternalUser). +func (m *InternalUserMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *InternalUserMutation) Fields() []string { + fields := make([]string, 0, 3) + if m.clerk_subject != nil { + fields = append(fields, internaluser.FieldClerkSubject) + } + if m.created_at != nil { + fields = append(fields, internaluser.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, internaluser.FieldUpdatedAt) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *InternalUserMutation) Field(name string) (ent.Value, bool) { + switch name { + case internaluser.FieldClerkSubject: + return m.ClerkSubject() + case internaluser.FieldCreatedAt: + return m.CreatedAt() + case internaluser.FieldUpdatedAt: + return m.UpdatedAt() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *InternalUserMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case internaluser.FieldClerkSubject: + return m.OldClerkSubject(ctx) + case internaluser.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case internaluser.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + } + return nil, fmt.Errorf("unknown InternalUser field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *InternalUserMutation) SetField(name string, value ent.Value) error { + switch name { + case internaluser.FieldClerkSubject: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetClerkSubject(v) + return nil + case internaluser.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case internaluser.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown InternalUser field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *InternalUserMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *InternalUserMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *InternalUserMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown InternalUser numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *InternalUserMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *InternalUserMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *InternalUserMutation) ClearField(name string) error { + return fmt.Errorf("unknown InternalUser nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *InternalUserMutation) ResetField(name string) error { + switch name { + case internaluser.FieldClerkSubject: + m.ResetClerkSubject() + return nil + case internaluser.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case internaluser.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + } + return fmt.Errorf("unknown InternalUser field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *InternalUserMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *InternalUserMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *InternalUserMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *InternalUserMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *InternalUserMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *InternalUserMutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *InternalUserMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown InternalUser unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *InternalUserMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown InternalUser edge %s", name) +} diff --git a/backend/ent/predicate/predicate.go b/backend/ent/predicate/predicate.go new file mode 100644 index 0000000..7228e2e --- /dev/null +++ b/backend/ent/predicate/predicate.go @@ -0,0 +1,10 @@ +// Code generated by ent, DO NOT EDIT. + +package predicate + +import ( + "entgo.io/ent/dialect/sql" +) + +// InternalUser is the predicate function for internaluser builders. +type InternalUser func(*sql.Selector) diff --git a/backend/ent/runtime.go b/backend/ent/runtime.go new file mode 100644 index 0000000..a3c9013 --- /dev/null +++ b/backend/ent/runtime.go @@ -0,0 +1,51 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/schema" + "github.com/google/uuid" +) + +// The init function reads all schema descriptors with runtime code +// (default values, validators, hooks and policies) and stitches it +// to their package variables. +func init() { + internaluserFields := schema.InternalUser{}.Fields() + _ = internaluserFields + // internaluserDescClerkSubject is the schema descriptor for clerk_subject field. + internaluserDescClerkSubject := internaluserFields[1].Descriptor() + // internaluser.ClerkSubjectValidator is a validator for the "clerk_subject" field. It is called by the builders before save. + internaluser.ClerkSubjectValidator = func() func(string) error { + validators := internaluserDescClerkSubject.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(clerk_subject string) error { + for _, fn := range fns { + if err := fn(clerk_subject); err != nil { + return err + } + } + return nil + } + }() + // internaluserDescCreatedAt is the schema descriptor for created_at field. + internaluserDescCreatedAt := internaluserFields[2].Descriptor() + // internaluser.DefaultCreatedAt holds the default value on creation for the created_at field. + internaluser.DefaultCreatedAt = internaluserDescCreatedAt.Default.(func() time.Time) + // internaluserDescUpdatedAt is the schema descriptor for updated_at field. + internaluserDescUpdatedAt := internaluserFields[3].Descriptor() + // internaluser.DefaultUpdatedAt holds the default value on creation for the updated_at field. + internaluser.DefaultUpdatedAt = internaluserDescUpdatedAt.Default.(func() time.Time) + // internaluser.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + internaluser.UpdateDefaultUpdatedAt = internaluserDescUpdatedAt.UpdateDefault.(func() time.Time) + // internaluserDescID is the schema descriptor for id field. + internaluserDescID := internaluserFields[0].Descriptor() + // internaluser.DefaultID holds the default value on creation for the id field. + internaluser.DefaultID = internaluserDescID.Default.(func() uuid.UUID) +} diff --git a/backend/ent/runtime/runtime.go b/backend/ent/runtime/runtime.go new file mode 100644 index 0000000..9dc180d --- /dev/null +++ b/backend/ent/runtime/runtime.go @@ -0,0 +1,10 @@ +// Code generated by ent, DO NOT EDIT. + +package runtime + +// The schema-stitching logic is generated in github.com/SourceSenseiTheRealOne/juntly/backend/ent/runtime.go + +const ( + Version = "v0.14.6" // Version of ent codegen. + Sum = "h1:/f2696BpwuWAEEG6PVGWflg6+Inrpq4pRWuNlWz/Skk=" // Sum of ent codegen. +) diff --git a/backend/ent/schema/internaluser.go b/backend/ent/schema/internaluser.go new file mode 100644 index 0000000..a331b46 --- /dev/null +++ b/backend/ent/schema/internaluser.go @@ -0,0 +1,34 @@ +package schema + +import ( + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/entsql" + entschema "entgo.io/ent/schema" + "entgo.io/ent/schema/field" + "github.com/google/uuid" +) + +type InternalUser struct { + ent.Schema +} + +func (InternalUser) Fields() []ent.Field { + return []ent.Field{ + field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable(), + field.String("clerk_subject").NotEmpty().MaxLen(255).Unique().Immutable(), + field.Time("created_at").Default(utcNow).Immutable(), + field.Time("updated_at").Default(utcNow).UpdateDefault(utcNow), + } +} + +func (InternalUser) Annotations() []entschema.Annotation { + return []entschema.Annotation{ + entsql.Annotation{Table: "internal_users"}, + } +} + +func utcNow() time.Time { + return time.Now().UTC() +} diff --git a/backend/ent/schema/internaluser_test.go b/backend/ent/schema/internaluser_test.go new file mode 100644 index 0000000..a809767 --- /dev/null +++ b/backend/ent/schema/internaluser_test.go @@ -0,0 +1,40 @@ +package schema_test + +import ( + "os" + "strings" + "testing" +) + +func TestInternalUserSchemaContract(t *testing.T) { + t.Parallel() + + contents, err := os.ReadFile("internaluser.go") + if err != nil { + t.Fatalf("read InternalUser schema: %v", err) + } + schema := string(contents) + + for _, requirement := range []string{ + "type InternalUser struct", + "field.UUID(\"id\"", + "Default(uuid.New)", + "field.String(\"clerk_subject\")", + "NotEmpty()", + "MaxLen(255)", + "Unique()", + "Immutable()", + "field.Time(\"created_at\")", + "field.Time(\"updated_at\")", + } { + if !strings.Contains(schema, requirement) { + t.Errorf("schema does not contain %q", requirement) + } + } + + for _, prohibited := range []string{"email", "display_name", "profile"} { + if strings.Contains(schema, prohibited) { + t.Errorf("schema must not include %q", prohibited) + } + } +} diff --git a/backend/ent/tx.go b/backend/ent/tx.go new file mode 100644 index 0000000..f9e777a --- /dev/null +++ b/backend/ent/tx.go @@ -0,0 +1,210 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "sync" + + "entgo.io/ent/dialect" +) + +// Tx is a transactional client that is created by calling Client.Tx(). +type Tx struct { + config + // InternalUser is the client for interacting with the InternalUser builders. + InternalUser *InternalUserClient + + // lazily loaded. + client *Client + clientOnce sync.Once + // ctx lives for the life of the transaction. It is + // the same context used by the underlying connection. + ctx context.Context +} + +type ( + // Committer is the interface that wraps the Commit method. + Committer interface { + Commit(context.Context, *Tx) error + } + + // The CommitFunc type is an adapter to allow the use of ordinary + // function as a Committer. If f is a function with the appropriate + // signature, CommitFunc(f) is a Committer that calls f. + CommitFunc func(context.Context, *Tx) error + + // CommitHook defines the "commit middleware". A function that gets a Committer + // and returns a Committer. For example: + // + // hook := func(next ent.Committer) ent.Committer { + // return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error { + // // Do some stuff before. + // if err := next.Commit(ctx, tx); err != nil { + // return err + // } + // // Do some stuff after. + // return nil + // }) + // } + // + CommitHook func(Committer) Committer +) + +// Commit calls f(ctx, m). +func (f CommitFunc) Commit(ctx context.Context, tx *Tx) error { + return f(ctx, tx) +} + +// Commit commits the transaction. +func (tx *Tx) Commit() error { + txDriver := tx.config.driver.(*txDriver) + var fn Committer = CommitFunc(func(context.Context, *Tx) error { + return txDriver.tx.Commit() + }) + txDriver.mu.Lock() + hooks := append([]CommitHook(nil), txDriver.onCommit...) + txDriver.mu.Unlock() + for i := len(hooks) - 1; i >= 0; i-- { + fn = hooks[i](fn) + } + return fn.Commit(tx.ctx, tx) +} + +// OnCommit adds a hook to call on commit. +func (tx *Tx) OnCommit(f CommitHook) { + txDriver := tx.config.driver.(*txDriver) + txDriver.mu.Lock() + txDriver.onCommit = append(txDriver.onCommit, f) + txDriver.mu.Unlock() +} + +type ( + // Rollbacker is the interface that wraps the Rollback method. + Rollbacker interface { + Rollback(context.Context, *Tx) error + } + + // The RollbackFunc type is an adapter to allow the use of ordinary + // function as a Rollbacker. If f is a function with the appropriate + // signature, RollbackFunc(f) is a Rollbacker that calls f. + RollbackFunc func(context.Context, *Tx) error + + // RollbackHook defines the "rollback middleware". A function that gets a Rollbacker + // and returns a Rollbacker. For example: + // + // hook := func(next ent.Rollbacker) ent.Rollbacker { + // return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error { + // // Do some stuff before. + // if err := next.Rollback(ctx, tx); err != nil { + // return err + // } + // // Do some stuff after. + // return nil + // }) + // } + // + RollbackHook func(Rollbacker) Rollbacker +) + +// Rollback calls f(ctx, m). +func (f RollbackFunc) Rollback(ctx context.Context, tx *Tx) error { + return f(ctx, tx) +} + +// Rollback rollbacks the transaction. +func (tx *Tx) Rollback() error { + txDriver := tx.config.driver.(*txDriver) + var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error { + return txDriver.tx.Rollback() + }) + txDriver.mu.Lock() + hooks := append([]RollbackHook(nil), txDriver.onRollback...) + txDriver.mu.Unlock() + for i := len(hooks) - 1; i >= 0; i-- { + fn = hooks[i](fn) + } + return fn.Rollback(tx.ctx, tx) +} + +// OnRollback adds a hook to call on rollback. +func (tx *Tx) OnRollback(f RollbackHook) { + txDriver := tx.config.driver.(*txDriver) + txDriver.mu.Lock() + txDriver.onRollback = append(txDriver.onRollback, f) + txDriver.mu.Unlock() +} + +// Client returns a Client that binds to current transaction. +func (tx *Tx) Client() *Client { + tx.clientOnce.Do(func() { + tx.client = &Client{config: tx.config} + tx.client.init() + }) + return tx.client +} + +func (tx *Tx) init() { + tx.InternalUser = NewInternalUserClient(tx.config) +} + +// txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. +// The idea is to support transactions without adding any extra code to the builders. +// When a builder calls to driver.Tx(), it gets the same dialect.Tx instance. +// Commit and Rollback are nop for the internal builders and the user must call one +// of them in order to commit or rollback the transaction. +// +// If a closed transaction is embedded in one of the generated entities, and the entity +// applies a query, for example: InternalUser.QueryXXX(), the query will be executed +// through the driver which created this transaction. +// +// Note that txDriver is not goroutine safe. +type txDriver struct { + // the driver we started the transaction from. + drv dialect.Driver + // tx is the underlying transaction. + tx dialect.Tx + // completion hooks. + mu sync.Mutex + onCommit []CommitHook + onRollback []RollbackHook +} + +// newTx creates a new transactional driver. +func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) { + tx, err := drv.Tx(ctx) + if err != nil { + return nil, err + } + return &txDriver{tx: tx, drv: drv}, nil +} + +// Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls +// from the internal builders. Should be called only by the internal builders. +func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil } + +// Dialect returns the dialect of the driver we started the transaction from. +func (tx *txDriver) Dialect() string { return tx.drv.Dialect() } + +// Close is a nop close. +func (*txDriver) Close() error { return nil } + +// Commit is a nop commit for the internal builders. +// User must call `Tx.Commit` in order to commit the transaction. +func (*txDriver) Commit() error { return nil } + +// Rollback is a nop rollback for the internal builders. +// User must call `Tx.Rollback` in order to rollback the transaction. +func (*txDriver) Rollback() error { return nil } + +// Exec calls tx.Exec. +func (tx *txDriver) Exec(ctx context.Context, query string, args, v any) error { + return tx.tx.Exec(ctx, query, args, v) +} + +// Query calls tx.Query. +func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error { + return tx.tx.Query(ctx, query, args, v) +} + +var _ dialect.Driver = (*txDriver)(nil) diff --git a/backend/go.mod b/backend/go.mod index 1639b0d..cbe207c 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,3 +1,33 @@ module github.com/SourceSenseiTheRealOne/juntly/backend go 1.26 + +require ( + entgo.io/ent v0.14.6 + github.com/clerk/clerk-sdk-go/v2 v2.7.0 + github.com/google/uuid v1.6.0 + github.com/jackc/pgx/v5 v5.10.0 +) + +require ( + ariga.io/atlas v0.36.2-0.20250730182955-2c6300d0a3e1 // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/bmatcuk/doublestar v1.3.4 // indirect + github.com/go-jose/go-jose/v3 v3.0.4 // indirect + github.com/go-openapi/inflect v0.19.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/hashicorp/hcl/v2 v2.18.1 // 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 + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/rogpeppe/go-internal v1.16.0 // indirect + github.com/zclconf/go-cty v1.14.4 // indirect + github.com/zclconf/go-cty-yaml v1.1.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/text v0.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/backend/go.sum b/backend/go.sum new file mode 100644 index 0000000..acb5c3a --- /dev/null +++ b/backend/go.sum @@ -0,0 +1,115 @@ +ariga.io/atlas v0.36.2-0.20250730182955-2c6300d0a3e1 h1:NPPfBaVZgz4LKBCIc0FbMogCjvXN+yGf7CZwotOwJo8= +ariga.io/atlas v0.36.2-0.20250730182955-2c6300d0a3e1/go.mod h1:Ex5l1xHsnWQUc3wYnrJ9gD7RUEzG76P7ZRQp8wNr0wc= +entgo.io/ent v0.14.6 h1:/f2696BpwuWAEEG6PVGWflg6+Inrpq4pRWuNlWz/Skk= +entgo.io/ent v0.14.6/go.mod h1:z46QBUdGC+BATwsedbDuREfSS0oSCV+csdEYlL4p73s= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= +github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +github.com/clerk/clerk-sdk-go/v2 v2.7.0 h1:Bc/hbqpXdPsaNpp9ppOzL3I0R5+8jVeZ5FvgB+bPv0o= +github.com/clerk/clerk-sdk-go/v2 v2.7.0/go.mod h1:ncFmsPwmD5WpGCNW5bJve862j/HQfpkzsshXYV/quJ8= +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-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= +github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= +github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo= +github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= +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.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +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/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +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/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= +github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= +github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0= +github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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.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/backend/internal/authn/clerk_verifier.go b/backend/internal/authn/clerk_verifier.go new file mode 100644 index 0000000..b129cc8 --- /dev/null +++ b/backend/internal/authn/clerk_verifier.go @@ -0,0 +1,85 @@ +package authn + +import ( + "context" + "errors" + "strings" + + clerk "github.com/clerk/clerk-sdk-go/v2" + clerkhttp "github.com/clerk/clerk-sdk-go/v2/http" + "github.com/clerk/clerk-sdk-go/v2/jwks" + clerkjwt "github.com/clerk/clerk-sdk-go/v2/jwt" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" +) + +var ( + ErrInvalidClerkVerifierConfig = errors.New("invalid Clerk verifier configuration") + ErrUnverifiedClerkToken = errors.New("unverified Clerk token") +) + +type ClerkVerifierConfig struct { + SecretKey string + JWTKey string + AuthorizedParties []string +} + +type clerkVerifier struct { + jwk *clerk.JSONWebKey + jwksClient *jwks.Client + authorizedParties map[string]struct{} +} + +func NewClerkVerifier(config ClerkVerifierConfig) (Verifier, error) { + parties := authorizedPartySet(config.AuthorizedParties) + if len(parties) == 0 { + return nil, ErrInvalidClerkVerifierConfig + } + + verifier := &clerkVerifier{authorizedParties: parties} + if jwtKey := strings.TrimSpace(config.JWTKey); jwtKey != "" { + params := &clerkhttp.AuthorizationParams{} + if err := clerkhttp.JSONWebKey(jwtKey)(params); err != nil { + return nil, ErrInvalidClerkVerifierConfig + } + verifier.jwk = params.JWK + return verifier, nil + } + + secretKey := strings.TrimSpace(config.SecretKey) + if secretKey == "" { + return nil, ErrInvalidClerkVerifierConfig + } + verifier.jwksClient = jwks.NewClient(&clerk.ClientConfig{ + BackendConfig: clerk.BackendConfig{Key: &secretKey}, + }) + return verifier, nil +} + +func (v *clerkVerifier) Verify(ctx context.Context, token string) (users.VerifiedIdentity, error) { + claims, err := clerkjwt.Verify(ctx, &clerkjwt.VerifyParams{ + Token: token, + JWK: v.jwk, + JWKSClient: v.jwksClient, + AuthorizedPartyHandler: v.authorizedParty, + }) + if err != nil || claims == nil || claims.SessionID == "" || strings.TrimSpace(claims.Subject) == "" { + return users.VerifiedIdentity{}, ErrUnverifiedClerkToken + } + + return users.VerifiedIdentity{Subject: claims.Subject}, nil +} + +func (v *clerkVerifier) authorizedParty(value string) bool { + _, ok := v.authorizedParties[value] + return ok +} + +func authorizedPartySet(values []string) map[string]struct{} { + parties := make(map[string]struct{}, len(values)) + for _, value := range values { + if value = strings.TrimSpace(value); value != "" { + parties[value] = struct{}{} + } + } + return parties +} diff --git a/backend/internal/authn/clerk_verifier_test.go b/backend/internal/authn/clerk_verifier_test.go new file mode 100644 index 0000000..c2cf0bb --- /dev/null +++ b/backend/internal/authn/clerk_verifier_test.go @@ -0,0 +1,68 @@ +package authn + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "testing" +) + +func TestNewClerkVerifierRejectsMissingVerificationMaterial(t *testing.T) { + t.Parallel() + + _, err := NewClerkVerifier(ClerkVerifierConfig{ + AuthorizedParties: []string{"http://localhost:4200"}, + }) + + if !errors.Is(err, ErrInvalidClerkVerifierConfig) { + t.Fatalf("error = %v, want ErrInvalidClerkVerifierConfig", err) + } +} + +func TestNewClerkVerifierRejectsMissingAuthorizedParties(t *testing.T) { + t.Parallel() + + _, err := NewClerkVerifier(ClerkVerifierConfig{JWTKey: "synthetic-public-key"}) + + if !errors.Is(err, ErrInvalidClerkVerifierConfig) { + t.Fatalf("error = %v, want ErrInvalidClerkVerifierConfig", err) + } +} + +func TestClerkVerifierRejectsMalformedTokenWithoutIdentity(t *testing.T) { + t.Parallel() + + verifier, err := NewClerkVerifier(ClerkVerifierConfig{ + JWTKey: testPublicKey(t), + AuthorizedParties: []string{"http://localhost:4200"}, + }) + if err != nil { + t.Fatalf("new Clerk verifier: %v", err) + } + + identity, err := verifier.Verify(context.Background(), "not-a-jwt") + + if err == nil { + t.Fatal("Verify error = nil, want rejection") + } + if identity.Subject != "" { + t.Fatalf("identity subject = %q, want empty", identity.Subject) + } +} + +func testPublicKey(t *testing.T) string { + t.Helper() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("generate RSA key: %v", err) + } + der, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) + if err != nil { + t.Fatalf("marshal public key: %v", err) + } + return string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der})) +} diff --git a/backend/internal/authn/middleware.go b/backend/internal/authn/middleware.go new file mode 100644 index 0000000..89280d0 --- /dev/null +++ b/backend/internal/authn/middleware.go @@ -0,0 +1,99 @@ +package authn + +import ( + "context" + "crypto/rand" + "encoding/hex" + "encoding/json" + "net/http" + "strings" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" +) + +type verifiedIdentityContextKey struct{} + +type unauthorizedResponse struct { + Error unauthorizedError `json:"error"` +} + +type unauthorizedError struct { + Code string `json:"code"` + Message string `json:"message"` + RequestID string `json:"requestId"` +} + +func RequireVerifiedIdentity(verifier Verifier, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) { + token, ok := bearerToken(request.Header.Get("Authorization")) + if !ok || verifier == nil { + writeUnauthorized(w, request.Header.Get("X-Request-ID")) + return + } + + identity, err := verifier.Verify(request.Context(), token) + if err != nil || strings.TrimSpace(identity.Subject) == "" { + writeUnauthorized(w, request.Header.Get("X-Request-ID")) + return + } + + next.ServeHTTP(w, request.WithContext(context.WithValue(request.Context(), verifiedIdentityContextKey{}, identity))) + }) +} + +func IdentityFromContext(ctx context.Context) (users.VerifiedIdentity, bool) { + identity, ok := ctx.Value(verifiedIdentityContextKey{}).(users.VerifiedIdentity) + return identity, ok +} + +func bearerToken(header string) (string, bool) { + parts := strings.Fields(header) + if len(parts) != 2 || parts[0] != "Bearer" || parts[1] == "" { + return "", false + } + return parts[1], true +} + +func writeUnauthorized(w http.ResponseWriter, requestedID string) { + requestID := requestIDFromHeader(requestedID) + w.Header().Set("X-Request-ID", requestID) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + _ = json.NewEncoder(w).Encode(unauthorizedResponse{Error: unauthorizedError{ + Code: "UNAUTHORIZED", + Message: "Unauthorized", + RequestID: requestID, + }}) +} + +func requestIDFromHeader(value string) string { + if validRequestID(value) { + return value + } + + var randomBytes [16]byte + if _, err := rand.Read(randomBytes[:]); err != nil { + return "req_unavailable" + } + + return "req_" + hex.EncodeToString(randomBytes[:]) +} + +func validRequestID(value string) bool { + if len(value) < 8 || len(value) > 128 { + return false + } + + for _, char := range value { + switch { + case char >= 'A' && char <= 'Z': + case char >= 'a' && char <= 'z': + case char >= '0' && char <= '9': + case char == '.', char == '_', char == ':', char == '-': + default: + return false + } + } + + return true +} diff --git a/backend/internal/authn/middleware_test.go b/backend/internal/authn/middleware_test.go new file mode 100644 index 0000000..da2be7b --- /dev/null +++ b/backend/internal/authn/middleware_test.go @@ -0,0 +1,186 @@ +package authn + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" +) + +func TestRequireVerifiedIdentityRejectsMissingBearerBeforeVerifier(t *testing.T) { + t.Parallel() + + verifier := &recordingVerifier{} + nextCalled := false + handler := RequireVerifiedIdentity(verifier, http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + nextCalled = true + })) + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/reconcile", nil) + request.Header.Set("X-Request-ID", "req_missing_bearer") + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusUnauthorized { + t.Fatalf("status = %d, want %d", response.Code, http.StatusUnauthorized) + } + if verifier.calls != 0 { + t.Fatalf("verifier calls = %d, want 0", verifier.calls) + } + if nextCalled { + t.Fatal("downstream handler was called") + } + assertUnauthorizedResponse(t, response, "req_missing_bearer") +} + +func TestRequireVerifiedIdentityRejectsMalformedAuthorizationBeforeVerifier(t *testing.T) { + t.Parallel() + + verifier := &recordingVerifier{} + nextCalled := false + handler := RequireVerifiedIdentity(verifier, http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + nextCalled = true + })) + request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/reconcile", nil) + request.Header.Set("Authorization", "Basic not-a-bearer-token") + response := httptest.NewRecorder() + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusUnauthorized { + t.Fatalf("status = %d, want %d", response.Code, http.StatusUnauthorized) + } + if verifier.calls != 0 { + t.Fatalf("verifier calls = %d, want 0", verifier.calls) + } + if nextCalled { + t.Fatal("downstream handler was called") + } +} + +func TestRequireVerifiedIdentityRejectsVerifierFailure(t *testing.T) { + t.Parallel() + + verifier := &recordingVerifier{err: errors.New("verification failed")} + nextCalled := false + handler := RequireVerifiedIdentity(verifier, http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + nextCalled = true + })) + request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/reconcile", nil) + request.Header.Set("Authorization", "Bearer synthetic-token") + response := httptest.NewRecorder() + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusUnauthorized { + t.Fatalf("status = %d, want %d", response.Code, http.StatusUnauthorized) + } + if verifier.calls != 1 { + t.Fatalf("verifier calls = %d, want 1", verifier.calls) + } + if nextCalled { + t.Fatal("downstream handler was called") + } +} + +func TestRequireVerifiedIdentityRejectsEmptyVerifiedSubject(t *testing.T) { + t.Parallel() + + verifier := &recordingVerifier{identity: users.VerifiedIdentity{}} + nextCalled := false + handler := RequireVerifiedIdentity(verifier, http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + nextCalled = true + })) + request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/reconcile", nil) + request.Header.Set("Authorization", "Bearer synthetic-token") + response := httptest.NewRecorder() + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusUnauthorized { + t.Fatalf("status = %d, want %d", response.Code, http.StatusUnauthorized) + } + if verifier.calls != 1 { + t.Fatalf("verifier calls = %d, want 1", verifier.calls) + } + if nextCalled { + t.Fatal("downstream handler was called") + } +} + +func TestRequireVerifiedIdentityPropagatesVerifiedSubjectToDownstreamHandler(t *testing.T) { + t.Parallel() + + identity := users.VerifiedIdentity{Subject: "user_synthetic"} + verifier := &recordingVerifier{identity: identity} + handler := RequireVerifiedIdentity(verifier, http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) { + actual, ok := IdentityFromContext(request.Context()) + if !ok { + t.Fatal("verified identity missing from context") + } + if actual != identity { + t.Fatalf("identity = %#v, want %#v", actual, identity) + } + w.WriteHeader(http.StatusNoContent) + })) + request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/reconcile", nil) + request.Header.Set("Authorization", "Bearer synthetic-token") + response := httptest.NewRecorder() + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusNoContent { + t.Fatalf("status = %d, want %d", response.Code, http.StatusNoContent) + } + if verifier.token != "synthetic-token" { + t.Fatalf("verifier token = %q, want synthetic-token", verifier.token) + } +} + +func assertUnauthorizedResponse(t *testing.T, response *httptest.ResponseRecorder, requestID string) { + t.Helper() + + if got := response.Header().Get("X-Request-ID"); got != requestID { + t.Fatalf("X-Request-ID = %q, want %q", got, requestID) + } + if got := response.Header().Get("Content-Type"); got != "application/json" { + t.Fatalf("Content-Type = %q, want application/json", got) + } + + var body map[string]any + if err := json.NewDecoder(response.Body).Decode(&body); err != nil { + t.Fatalf("decode response: %v", err) + } + encoded, err := json.Marshal(body) + if err != nil { + t.Fatalf("marshal response: %v", err) + } + want, err := json.Marshal(map[string]any{"error": map[string]any{ + "code": "UNAUTHORIZED", + "message": "Unauthorized", + "requestId": requestID, + }}) + if err != nil { + t.Fatalf("marshal expected response: %v", err) + } + if string(encoded) != string(want) { + t.Fatalf("error body = %s, want %s", encoded, want) + } +} + +type recordingVerifier struct { + identity users.VerifiedIdentity + err error + calls int + token string +} + +func (v *recordingVerifier) Verify(_ context.Context, token string) (users.VerifiedIdentity, error) { + v.calls++ + v.token = token + return v.identity, v.err +} diff --git a/backend/internal/authn/verifier.go b/backend/internal/authn/verifier.go new file mode 100644 index 0000000..938ecd4 --- /dev/null +++ b/backend/internal/authn/verifier.go @@ -0,0 +1,11 @@ +package authn + +import ( + "context" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" +) + +type Verifier interface { + Verify(context.Context, string) (users.VerifiedIdentity, error) +} diff --git a/backend/internal/httpapi/health_handler.go b/backend/internal/httpapi/health_handler.go index cf9844f..dae1ad5 100644 --- a/backend/internal/httpapi/health_handler.go +++ b/backend/internal/httpapi/health_handler.go @@ -6,6 +6,7 @@ import ( "encoding/json" "net/http" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/authn" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/health" ) @@ -34,9 +35,10 @@ func (h HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(h.service.Check(requestID)) } -func NewRouter(service health.Service) http.Handler { +func NewRouter(service health.Service, verifier authn.Verifier, reconcileService ReconcileService) http.Handler { mux := http.NewServeMux() mux.Handle("/api/v1/health", NewHealthHandler(service)) + mux.Handle("/api/v1/auth/reconcile", authn.RequireVerifiedIdentity(verifier, NewReconcileHandler(reconcileService))) return mux } diff --git a/backend/internal/httpapi/openapi_contract_test.go b/backend/internal/httpapi/openapi_contract_test.go new file mode 100644 index 0000000..e199c74 --- /dev/null +++ b/backend/internal/httpapi/openapi_contract_test.go @@ -0,0 +1,39 @@ +package httpapi_test + +import ( + "os" + "strings" + "testing" +) + +func TestReconcileOpenAPIContract(t *testing.T) { + t.Parallel() + + contract, err := os.ReadFile("../../../openapi/juntly-api.v1.yaml") + if err != nil { + t.Fatalf("read OpenAPI contract: %v", err) + } + contents := string(contract) + + for _, required := range []string{ + "/api/v1/auth/reconcile:", + "post:", + "operationId: reconcileInternalUser", + "clerkSession: []", + "InternalUserResponse:", + "format: uuid", + "createdAt:", + "format: date-time", + "UNAUTHORIZED", + "SERVICE_UNAVAILABLE", + } { + if !strings.Contains(contents, required) { + t.Fatalf("OpenAPI contract does not contain %q", required) + } + } + + operation := contents[strings.Index(contents, "/api/v1/auth/reconcile:"):] + if strings.Contains(operation, "requestBody:") { + t.Fatal("reconciliation operation must not accept a request body") + } +} diff --git a/backend/internal/httpapi/reconcile_handler.go b/backend/internal/httpapi/reconcile_handler.go new file mode 100644 index 0000000..7e1df09 --- /dev/null +++ b/backend/internal/httpapi/reconcile_handler.go @@ -0,0 +1,80 @@ +package httpapi + +import ( + "context" + "encoding/json" + "net/http" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/authn" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" + "github.com/google/uuid" +) + +type ReconcileService interface { + Reconcile(context.Context, users.VerifiedIdentity) (users.InternalUser, bool, error) +} + +type reconcileHandler struct { + service ReconcileService +} + +type internalUserResponse struct { + ID uuid.UUID `json:"id"` + CreatedAt string `json:"createdAt"` +} + +type apiErrorResponse struct { + Error apiErrorDetail `json:"error"` +} + +type apiErrorDetail struct { + Code string `json:"code"` + Message string `json:"message"` + RequestID string `json:"requestId"` +} + +func NewReconcileHandler(service ReconcileService) http.Handler { + return reconcileHandler{service: service} +} + +func (h reconcileHandler) ServeHTTP(w http.ResponseWriter, request *http.Request) { + if request.Method != http.MethodPost { + w.Header().Set("Allow", http.MethodPost) + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + + requestID := requestIDFromHeader(request.Header.Get(RequestIDHeader)) + identity, ok := authn.IdentityFromContext(request.Context()) + if !ok || h.service == nil { + writeAPIError(w, http.StatusUnauthorized, "UNAUTHORIZED", "Unauthorized", requestID) + return + } + + user, _, err := h.service.Reconcile(request.Context(), identity) + if err != nil { + writeAPIError(w, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "Service unavailable", requestID) + return + } + + writeJSON(w, http.StatusOK, internalUserResponse{ + ID: user.ID, + CreatedAt: user.CreatedAt.UTC().Format(time.RFC3339Nano), + }, requestID) +} + +func writeAPIError(w http.ResponseWriter, status int, code, message, requestID string) { + writeJSON(w, status, apiErrorResponse{Error: apiErrorDetail{ + Code: code, + Message: message, + RequestID: requestID, + }}, requestID) +} + +func writeJSON(w http.ResponseWriter, status int, body any, requestID string) { + w.Header().Set(RequestIDHeader, requestID) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(body) +} diff --git a/backend/internal/httpapi/reconcile_handler_test.go b/backend/internal/httpapi/reconcile_handler_test.go new file mode 100644 index 0000000..99a6cda --- /dev/null +++ b/backend/internal/httpapi/reconcile_handler_test.go @@ -0,0 +1,170 @@ +package httpapi_test + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/authn" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/httpapi" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" + "github.com/google/uuid" +) + +func TestReconcileHandlerReturnsUnauthorizedWithoutVerifiedIdentity(t *testing.T) { + t.Parallel() + + service := &recordingReconcileService{} + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/reconcile", nil) + request.Header.Set(httpapi.RequestIDHeader, "req_reconcile_unauthorized") + + httpapi.NewReconcileHandler(service).ServeHTTP(response, request) + + if response.Code != http.StatusUnauthorized { + t.Fatalf("status = %d, want %d", response.Code, http.StatusUnauthorized) + } + if service.calls != 0 { + t.Fatalf("service calls = %d, want 0", service.calls) + } + assertErrorResponse(t, response, "UNAUTHORIZED", "Unauthorized", "req_reconcile_unauthorized") +} + +func TestReconcileHandlerReturnsOpaqueInternalUser(t *testing.T) { + t.Parallel() + + createdAt := time.Date(2026, 8, 21, 12, 0, 0, 123456000, time.UTC) + service := &recordingReconcileService{user: users.InternalUser{ + ID: uuid.MustParse("7b7b7d7e-38f9-4f0c-8a10-0fce9cf6f82b"), + CreatedAt: createdAt, + }} + handler := authn.RequireVerifiedIdentity(staticVerifier{identity: users.VerifiedIdentity{Subject: "user_synthetic"}}, httpapi.NewReconcileHandler(service)) + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/reconcile", nil) + request.Header.Set("Authorization", "Bearer synthetic-token") + request.Header.Set(httpapi.RequestIDHeader, "req_reconcile_success") + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusOK { + t.Fatalf("status = %d, want %d", response.Code, http.StatusOK) + } + if response.Header().Get(httpapi.RequestIDHeader) != "req_reconcile_success" { + t.Fatalf("request ID header = %q", response.Header().Get(httpapi.RequestIDHeader)) + } + if service.identity.Subject != "user_synthetic" { + t.Fatalf("service subject = %q, want user_synthetic", service.identity.Subject) + } + + var body map[string]any + if err := json.NewDecoder(response.Body).Decode(&body); err != nil { + t.Fatalf("decode response: %v", err) + } + if len(body) != 2 { + t.Fatalf("response field count = %d, want 2 (%#v)", len(body), body) + } + if body["id"] != "7b7b7d7e-38f9-4f0c-8a10-0fce9cf6f82b" { + t.Fatalf("id = %#v", body["id"]) + } + if body["createdAt"] != "2026-08-21T12:00:00.123456Z" { + t.Fatalf("createdAt = %#v", body["createdAt"]) + } + serialized := mustJSON(t, body) + if strings.Contains(serialized, "user_synthetic") || strings.Contains(serialized, "synthetic-token") { + t.Fatalf("response leaks identity material: %s", serialized) + } +} + +func TestReconcileHandlerReturnsSafeUnavailableForDependencyFailure(t *testing.T) { + t.Parallel() + + service := &recordingReconcileService{err: errors.New("database at internal-host rejected user_synthetic")} + handler := authn.RequireVerifiedIdentity(staticVerifier{identity: users.VerifiedIdentity{Subject: "user_synthetic"}}, httpapi.NewReconcileHandler(service)) + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/reconcile", nil) + request.Header.Set("Authorization", "Bearer synthetic-token") + request.Header.Set(httpapi.RequestIDHeader, "req_reconcile_unavailable") + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusServiceUnavailable { + t.Fatalf("status = %d, want %d", response.Code, http.StatusServiceUnavailable) + } + assertErrorResponse(t, response, "SERVICE_UNAVAILABLE", "Service unavailable", "req_reconcile_unavailable") +} + +func TestReconcileHandlerRejectsNonPOSTMethods(t *testing.T) { + t.Parallel() + + service := &recordingReconcileService{} + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/api/v1/auth/reconcile", nil) + + httpapi.NewReconcileHandler(service).ServeHTTP(response, request) + + if response.Code != http.StatusMethodNotAllowed { + t.Fatalf("status = %d, want %d", response.Code, http.StatusMethodNotAllowed) + } + if allow := response.Header().Get("Allow"); allow != http.MethodPost { + t.Fatalf("Allow = %q, want %q", allow, http.MethodPost) + } + if service.calls != 0 { + t.Fatalf("service calls = %d, want 0", service.calls) + } +} + +type staticVerifier struct { + identity users.VerifiedIdentity + err error +} + +func (v staticVerifier) Verify(context.Context, string) (users.VerifiedIdentity, error) { + return v.identity, v.err +} + +type recordingReconcileService struct { + user users.InternalUser + err error + calls int + identity users.VerifiedIdentity +} + +func (s *recordingReconcileService) Reconcile(_ context.Context, identity users.VerifiedIdentity) (users.InternalUser, bool, error) { + s.calls++ + s.identity = identity + return s.user, false, s.err +} + +func assertErrorResponse(t *testing.T, response *httptest.ResponseRecorder, code, message, requestID string) { + t.Helper() + + if response.Header().Get(httpapi.RequestIDHeader) != requestID { + t.Fatalf("request ID header = %q, want %q", response.Header().Get(httpapi.RequestIDHeader), requestID) + } + var body map[string]any + if err := json.NewDecoder(response.Body).Decode(&body); err != nil { + t.Fatalf("decode response: %v", err) + } + if mustJSON(t, body) != mustJSON(t, map[string]any{"error": map[string]any{ + "code": code, + "message": message, + "requestId": requestID, + }}) { + t.Fatalf("error body = %#v", body) + } +} + +func mustJSON(t *testing.T, value any) string { + t.Helper() + + encoded, err := json.Marshal(value) + if err != nil { + t.Fatalf("marshal JSON: %v", err) + } + return string(encoded) +} diff --git a/backend/internal/httpapi/router_test.go b/backend/internal/httpapi/router_test.go new file mode 100644 index 0000000..4421bc5 --- /dev/null +++ b/backend/internal/httpapi/router_test.go @@ -0,0 +1,67 @@ +package httpapi_test + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/health" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/httpapi" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" + "github.com/google/uuid" +) + +func TestRouterLeavesHealthPublicAndProtectsReconciliation(t *testing.T) { + t.Parallel() + + verifier := &routerVerifier{identity: users.VerifiedIdentity{Subject: "user_synthetic"}} + reconcileService := &recordingReconcileService{user: users.InternalUser{ + ID: uuid.MustParse("7b7b7d7e-38f9-4f0c-8a10-0fce9cf6f82b"), + CreatedAt: time.Date(2026, 8, 21, 12, 0, 0, 0, time.UTC), + }} + healthService := health.NewService("0.1.0", func() time.Time { + return time.Date(2026, 8, 21, 12, 0, 0, 0, time.UTC) + }) + router := httpapi.NewRouter(healthService, verifier, reconcileService) + + healthResponse := httptest.NewRecorder() + router.ServeHTTP(healthResponse, httptest.NewRequest(http.MethodGet, "/api/v1/health", nil)) + if healthResponse.Code != http.StatusOK { + t.Fatalf("health status = %d, want %d", healthResponse.Code, http.StatusOK) + } + if verifier.calls != 0 { + t.Fatalf("verifier calls after health = %d, want 0", verifier.calls) + } + + unauthorizedResponse := httptest.NewRecorder() + router.ServeHTTP(unauthorizedResponse, httptest.NewRequest(http.MethodPost, "/api/v1/auth/reconcile", nil)) + if unauthorizedResponse.Code != http.StatusUnauthorized { + t.Fatalf("unauthorized reconciliation status = %d, want %d", unauthorizedResponse.Code, http.StatusUnauthorized) + } + if verifier.calls != 0 { + t.Fatalf("verifier calls after missing bearer = %d, want 0", verifier.calls) + } + + authorizedRequest := httptest.NewRequest(http.MethodPost, "/api/v1/auth/reconcile", nil) + authorizedRequest.Header.Set("Authorization", "Bearer synthetic-token") + authorizedResponse := httptest.NewRecorder() + router.ServeHTTP(authorizedResponse, authorizedRequest) + if authorizedResponse.Code != http.StatusOK { + t.Fatalf("authorized reconciliation status = %d, want %d", authorizedResponse.Code, http.StatusOK) + } + if verifier.calls != 1 { + t.Fatalf("verifier calls after valid bearer = %d, want 1", verifier.calls) + } +} + +type routerVerifier struct { + identity users.VerifiedIdentity + calls int +} + +func (v *routerVerifier) Verify(context.Context, string) (users.VerifiedIdentity, error) { + v.calls++ + return v.identity, nil +} diff --git a/backend/internal/users/ent_repository.go b/backend/internal/users/ent_repository.go new file mode 100644 index 0000000..5c2c6c0 --- /dev/null +++ b/backend/internal/users/ent_repository.go @@ -0,0 +1,55 @@ +package users + +import ( + "context" + "errors" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/ent" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" +) + +type entRepository struct { + client *ent.Client +} + +func NewEntRepository(client *ent.Client) Repository { + return entRepository{client: client} +} + +func (r entRepository) FindBySubject(ctx context.Context, subject string) (InternalUser, bool, error) { + if r.client == nil { + return InternalUser{}, false, errors.New("Ent client is nil") + } + + entity, err := r.client.InternalUser.Query().Where(internaluser.ClerkSubjectEQ(subject)).Only(ctx) + if err != nil { + if ent.IsNotFound(err) { + return InternalUser{}, false, nil + } + return InternalUser{}, false, err + } + return internalUserFromEnt(entity), true, nil +} + +func (r entRepository) Create(ctx context.Context, subject string) (InternalUser, error) { + if r.client == nil { + return InternalUser{}, errors.New("Ent client is nil") + } + + entity, err := r.client.InternalUser.Create().SetClerkSubject(subject).Save(ctx) + if err != nil { + if ent.IsConstraintError(err) { + return InternalUser{}, ErrSubjectConflict + } + return InternalUser{}, err + } + return internalUserFromEnt(entity), nil +} + +func internalUserFromEnt(entity *ent.InternalUser) InternalUser { + return InternalUser{ + ID: entity.ID, + CreatedAt: entity.CreatedAt.UTC().Truncate(time.Microsecond), + } +} diff --git a/backend/internal/users/ent_repository_test.go b/backend/internal/users/ent_repository_test.go new file mode 100644 index 0000000..14825ee --- /dev/null +++ b/backend/internal/users/ent_repository_test.go @@ -0,0 +1,135 @@ +package users + +import ( + "context" + "database/sql" + "os" + "sync" + "testing" + + "github.com/SourceSenseiTheRealOne/juntly/backend/ent" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/google/uuid" + _ "github.com/jackc/pgx/v5/stdlib" + "entgo.io/ent/dialect" + entsql "entgo.io/ent/dialect/sql" +) + +func TestEntRepositoryFindsAndCreatesByExactSubject(t *testing.T) { + client := openIntegrationClient(t) + repository := NewEntRepository(client) + ctx := context.Background() + subject := "test_" + uuid.NewString() + + created, err := repository.Create(ctx, subject) + if err != nil { + t.Fatalf("create: %v", err) + } + t.Cleanup(func() { + if err := client.InternalUser.DeleteOneID(created.ID).Exec(ctx); err != nil { + t.Errorf("cleanup internal user: %v", err) + } + }) + + found, exists, err := repository.FindBySubject(ctx, subject) + if err != nil { + t.Fatalf("find: %v", err) + } + if !exists { + t.Fatal("exists = false, want true") + } + if found != created { + t.Fatalf("found = %#v, want %#v", found, created) + } + + _, exists, err = repository.FindBySubject(ctx, subject+"_other") + if err != nil { + t.Fatalf("find missing: %v", err) + } + if exists { + t.Fatal("exists = true for a different subject") + } +} + +func TestReconcileConcurrentSameSubjectProducesOneStableRow(t *testing.T) { + client := openIntegrationClient(t) + repository := NewEntRepository(client) + service := NewService(repository) + ctx := context.Background() + subject := "test_" + uuid.NewString() + + const attempts = 8 + start := make(chan struct{}) + results := make(chan InternalUser, attempts) + errs := make(chan error, attempts) + var workers sync.WaitGroup + for range attempts { + workers.Add(1) + go func() { + defer workers.Done() + <-start + user, _, err := service.Reconcile(ctx, VerifiedIdentity{Subject: subject}) + if err != nil { + errs <- err + return + } + results <- user + }() + } + close(start) + workers.Wait() + close(results) + close(errs) + + for err := range errs { + t.Fatalf("reconcile: %v", err) + } + + var stable InternalUser + for user := range results { + if stable.ID == uuid.Nil { + stable = user + continue + } + if user != stable { + t.Fatalf("user = %#v, want stable %#v", user, stable) + } + } + if stable.ID == uuid.Nil { + t.Fatal("no reconciliation result") + } + t.Cleanup(func() { + if _, err := client.InternalUser.Delete().Where(internaluser.IDEQ(stable.ID)).Exec(ctx); err != nil { + t.Errorf("cleanup internal user: %v", err) + } + }) + + count, err := client.InternalUser.Query().Where(internaluser.ClerkSubjectEQ(subject)).Count(ctx) + if err != nil { + t.Fatalf("count subject mappings: %v", err) + } + if count != 1 { + t.Fatalf("count = %d, want 1", count) + } +} + +func openIntegrationClient(t *testing.T) *ent.Client { + t.Helper() + + databaseURL := os.Getenv("TEST_DATABASE_URL") + if databaseURL == "" { + t.Skip("TEST_DATABASE_URL is required for PostgreSQL integration tests") + } + + database, err := sql.Open("pgx", databaseURL) + if err != nil { + t.Fatalf("open pgx database: %v", err) + } + client := ent.NewClient(ent.Driver(entsql.OpenDB(dialect.Postgres, database))) + t.Cleanup(func() { + if err := client.Close(); err != nil { + t.Errorf("close Ent client: %v", err) + } + }) + return client +} diff --git a/backend/internal/users/migration_contract_test.go b/backend/internal/users/migration_contract_test.go new file mode 100644 index 0000000..09c7bfa --- /dev/null +++ b/backend/internal/users/migration_contract_test.go @@ -0,0 +1,48 @@ +package users_test + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestCreateInternalUsersMigrationContract(t *testing.T) { + t.Parallel() + + migrationDirectory := filepath.Join("..", "..", "..", "supabase", "migrations") + entries, err := os.ReadDir(migrationDirectory) + if err != nil { + t.Fatalf("read migration directory: %v", err) + } + + var migrationPath string + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), "_create_internal_users.sql") { + migrationPath = filepath.Join(migrationDirectory, entry.Name()) + break + } + } + if migrationPath == "" { + t.Fatal("internal-users migration was not found") + } + + contents, err := os.ReadFile(migrationPath) + if err != nil { + t.Fatalf("read migration: %v", err) + } + migration := strings.ToLower(string(contents)) + + for _, requirement := range []string{ + "create table public.internal_users", + "id uuid primary key", + "clerk_subject text not null unique", + "created_at timestamptz not null", + "updated_at timestamptz not null", + "char_length(clerk_subject) between 1 and 255", + } { + if !strings.Contains(migration, requirement) { + t.Errorf("migration does not contain %q", requirement) + } + } +} diff --git a/backend/internal/users/model.go b/backend/internal/users/model.go new file mode 100644 index 0000000..bb42ccd --- /dev/null +++ b/backend/internal/users/model.go @@ -0,0 +1,18 @@ +package users + +import ( + "time" + + "github.com/google/uuid" +) + +const maxSubjectLength = 255 + +type VerifiedIdentity struct { + Subject string +} + +type InternalUser struct { + ID uuid.UUID + CreatedAt time.Time +} diff --git a/backend/internal/users/repository.go b/backend/internal/users/repository.go new file mode 100644 index 0000000..9a5fba9 --- /dev/null +++ b/backend/internal/users/repository.go @@ -0,0 +1,16 @@ +package users + +import ( + "context" + "errors" +) + +var ( + ErrSubjectConflict = errors.New("internal user subject conflict") + ErrUnavailable = errors.New("internal user persistence unavailable") +) + +type Repository interface { + FindBySubject(context.Context, string) (InternalUser, bool, error) + Create(context.Context, string) (InternalUser, error) +} diff --git a/backend/internal/users/service.go b/backend/internal/users/service.go new file mode 100644 index 0000000..dcc9fb8 --- /dev/null +++ b/backend/internal/users/service.go @@ -0,0 +1,57 @@ +package users + +import ( + "context" + "errors" + "strings" + "unicode/utf8" +) + +var ErrInvalidIdentity = errors.New("invalid verified identity") + +type Service interface { + Reconcile(context.Context, VerifiedIdentity) (InternalUser, bool, error) +} + +type service struct { + repository Repository +} + +func NewService(repository Repository) Service { + return service{repository: repository} +} + +func (s service) Reconcile(ctx context.Context, identity VerifiedIdentity) (InternalUser, bool, error) { + if !validSubject(identity.Subject) { + return InternalUser{}, false, ErrInvalidIdentity + } + if s.repository == nil { + return InternalUser{}, false, ErrUnavailable + } + + existing, found, err := s.repository.FindBySubject(ctx, identity.Subject) + if err != nil { + return InternalUser{}, false, ErrUnavailable + } + if found { + return existing, false, nil + } + + created, err := s.repository.Create(ctx, identity.Subject) + if err == nil { + return created, true, nil + } + if !errors.Is(err, ErrSubjectConflict) { + return InternalUser{}, false, ErrUnavailable + } + + winner, found, err := s.repository.FindBySubject(ctx, identity.Subject) + if err != nil || !found { + return InternalUser{}, false, ErrUnavailable + } + return winner, false, nil +} + +func validSubject(subject string) bool { + return strings.TrimSpace(subject) != "" && utf8.RuneCountInString(subject) <= maxSubjectLength +} diff --git a/backend/internal/users/service_test.go b/backend/internal/users/service_test.go new file mode 100644 index 0000000..052c126 --- /dev/null +++ b/backend/internal/users/service_test.go @@ -0,0 +1,147 @@ +package users + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/google/uuid" +) + +func TestReconcileRejectsInvalidSubjectBeforeRepositoryAccess(t *testing.T) { + t.Parallel() + + repository := &recordingRepository{} + _, _, err := NewService(repository).Reconcile(context.Background(), VerifiedIdentity{}) + + if !errors.Is(err, ErrInvalidIdentity) { + t.Fatalf("error = %v, want ErrInvalidIdentity", err) + } + if repository.findCalls != 0 || repository.createCalls != 0 { + t.Fatalf("repository calls = find:%d create:%d, want none", repository.findCalls, repository.createCalls) + } +} + +func TestReconcileCreatesFirstMapping(t *testing.T) { + t.Parallel() + + created := InternalUser{ + ID: uuid.MustParse("7b7b7d7e-38f9-4f0c-8a10-0fce9cf6f82b"), + CreatedAt: time.Date(2026, 8, 21, 12, 0, 0, 0, time.UTC), + } + repository := &recordingRepository{createResult: created} + + user, wasCreated, err := NewService(repository).Reconcile(context.Background(), VerifiedIdentity{Subject: "user_123"}) + + if err != nil { + t.Fatalf("reconcile: %v", err) + } + if !wasCreated { + t.Fatal("wasCreated = false, want true") + } + if user != created { + t.Fatalf("user = %#v, want %#v", user, created) + } + if repository.findCalls != 1 || repository.createCalls != 1 { + t.Fatalf("repository calls = find:%d create:%d, want 1:1", repository.findCalls, repository.createCalls) + } +} + +func TestReconcileReturnsExistingMappingWithoutCreate(t *testing.T) { + t.Parallel() + + existing := InternalUser{ + ID: uuid.MustParse("7b7b7d7e-38f9-4f0c-8a10-0fce9cf6f82b"), + CreatedAt: time.Date(2026, 8, 21, 12, 0, 0, 0, time.UTC), + } + repository := &recordingRepository{findResults: []findResult{{user: existing, found: true}}} + + user, wasCreated, err := NewService(repository).Reconcile(context.Background(), VerifiedIdentity{Subject: "user_123"}) + + if err != nil { + t.Fatalf("reconcile: %v", err) + } + if wasCreated { + t.Fatal("wasCreated = true, want false") + } + if user != existing { + t.Fatalf("user = %#v, want %#v", user, existing) + } + if repository.findCalls != 1 || repository.createCalls != 0 { + t.Fatalf("repository calls = find:%d create:%d, want 1:0", repository.findCalls, repository.createCalls) + } +} + +func TestReconcileReloadsWinnerAfterUniqueConflict(t *testing.T) { + t.Parallel() + + winner := InternalUser{ + ID: uuid.MustParse("7b7b7d7e-38f9-4f0c-8a10-0fce9cf6f82b"), + CreatedAt: time.Date(2026, 8, 21, 12, 0, 0, 0, time.UTC), + } + repository := &recordingRepository{ + findResults: []findResult{{}, {user: winner, found: true}}, + createErr: ErrSubjectConflict, + } + + user, wasCreated, err := NewService(repository).Reconcile(context.Background(), VerifiedIdentity{Subject: "user_123"}) + + if err != nil { + t.Fatalf("reconcile: %v", err) + } + if wasCreated { + t.Fatal("wasCreated = true, want false") + } + if user != winner { + t.Fatalf("user = %#v, want %#v", user, winner) + } + if repository.findCalls != 2 || repository.createCalls != 1 { + t.Fatalf("repository calls = find:%d create:%d, want 2:1", repository.findCalls, repository.createCalls) + } +} + +func TestReconcileReturnsControlledFailureForRepositoryError(t *testing.T) { + t.Parallel() + + repository := &recordingRepository{findResults: []findResult{{err: errors.New("database unavailable")}}} + + _, _, err := NewService(repository).Reconcile(context.Background(), VerifiedIdentity{Subject: "user_123"}) + + if !errors.Is(err, ErrUnavailable) { + t.Fatalf("error = %v, want ErrUnavailable", err) + } + if repository.createCalls != 0 { + t.Fatalf("create calls = %d, want 0", repository.createCalls) + } +} + +type findResult struct { + user InternalUser + found bool + err error +} + +type recordingRepository struct { + findResults []findResult + createResult InternalUser + createErr error + findCalls int + createCalls int +} + +func (r *recordingRepository) FindBySubject(_ context.Context, _ string) (InternalUser, bool, error) { + r.findCalls++ + if len(r.findResults) == 0 { + return InternalUser{}, false, nil + } + + result := r.findResults[0] + r.findResults = r.findResults[1:] + return result.user, result.found, result.err +} + +func (r *recordingRepository) Create(_ context.Context, _ string) (InternalUser, error) { + r.createCalls++ + return r.createResult, r.createErr +} diff --git a/compose.yaml b/compose.yaml index 1118cf2..5e0a9ea 100644 --- a/compose.yaml +++ b/compose.yaml @@ -4,6 +4,10 @@ services: context: ./backend environment: JUNTLY_API_ADDR: :8080 + DATABASE_URL: ${DATABASE_URL:-} + CLERK_SECRET_KEY: ${CLERK_SECRET_KEY:-} + CLERK_JWT_KEY: ${CLERK_JWT_KEY:-} + CLERK_AUTHORIZED_PARTIES: ${CLERK_AUTHORIZED_PARTIES:-http://localhost:4200} healthcheck: test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8080/api/v1/health || exit 1"] interval: 5s diff --git a/docs/superpowers/plans/2026-08-21-durable-internal-user-mapping.md b/docs/superpowers/plans/2026-08-21-durable-internal-user-mapping.md new file mode 100644 index 0000000..c85dcb2 --- /dev/null +++ b/docs/superpowers/plans/2026-08-21-durable-internal-user-mapping.md @@ -0,0 +1,511 @@ +# Durable Internal User Mapping Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:executing-plans` task-by-task. Every behavior change follows RED → GREEN → REFACTOR. + +**Goal:** Persist one opaque Juntly UUID per Clerk-verified subject, expose it through a same-origin reconciliation route, and prove idempotency against disposable Supabase PostgreSQL. + +**Architecture:** `feature/internal-user-mapping` consumes the immutable health/API parent (`727f288`) and the committed Clerk foundation (`e154f44`) as a local stacked branch. The browser calls a Next.js BFF with no identity body; the BFF obtains a current Clerk session token, Go verifies it, and a provider-neutral service reconciles the verified subject through Ent into the single `public.internal_users` table. + +**Tech Stack:** Next.js 16.3.1, Clerk Next.js 7.7.9, Go 1.26, Clerk Go SDK v2.7.0, Ent v0.14.6, pgx v5.10.0, Supabase CLI 2.78.1, PostgreSQL, OpenAPI 3.1, Vitest. + +## Global Constraints + +- Preserve `feature/clerk-identity` and `feature/health-tracer`; consume their local commits rather than copying their source. +- The browser must never submit a Clerk subject, internal ID, profile field, role, or entitlement. +- `internal_users` stores only opaque UUID, immutable verified subject, and timestamps. No email, name, provider profile, metadata, or token is persisted. +- SQL in `supabase/migrations/` is the migration authority. Ent reflects SQL; API startup never auto-migrates. +- Bearer verification happens in Go; BFF session validation is defense in depth. +- `JUNTLY_API_ORIGIN`, database URLs, Clerk/Supabase credentials, tokens, and generated keys stay server-only and ignored. Do not print, stage, or commit values. +- The BFF maps backend configuration/upstream/malformed-payload/request-ID failures to privacy-safe documented errors; it never exposes backend topology. +- Preserve `localhost:4200` as the canonical browser auth origin. +- Do not add marketplace, profile, payment, billing, role, entitlement, webhook, or provider-ownership scope. +- No push, PR, remote Supabase action, Clerk write, or mapping-feature commit is authorized by this plan. Task 1 alone is authorized to create the local merge commit required to stack `e154f44` on the health/API base; pause after verification for an explicit delivery decision. + +--- + +### Task 1: Build the local stacked predecessor baseline + +**Objective:** Put the already verified health/API and Clerk foundations in the implementation branch without modifying their source commits. + +**Files:** +- Merge input: local commit `e154f44` from `feature/clerk-identity` +- Verify: `backend/go.mod`, `frontend/package.json`, `frontend/src/proxy.ts`, `frontend/src/app/api/v1/health/route.ts` + +**Interfaces:** +- Consumes: `727f288` as `feature/internal-user-mapping` base and `e154f44` as the Clerk source commit. +- Produces: one clean branch where the Go API and Clerk server helpers are both available. + +- [ ] **Step 1: Capture the immutable branch inputs** + +Run: + +```bash +git status --short --branch +git rev-parse HEAD development feature/health-tracer feature/clerk-identity +git merge-base --is-ancestor 727f288 HEAD +git merge-base --is-ancestor e154f44 HEAD +``` + +Expected: clean branch, health is an ancestor, Clerk is not yet an ancestor. + +- [ ] **Step 2: Merge only the local Clerk foundation** + +Run: + +```bash +git merge --no-ff e154f44 -m "chore: stack Clerk identity foundation for user mapping" +``` + +Resolve only overlapping health/Clerk source by preserving both route sets and their tests. Do not alter either predecessor commit or update a remote. + +- [ ] **Step 3: Verify the merge result** + +Run: + +```bash +npm --prefix frontend run verify +cd backend && go test ./... +cd .. && git diff --check && codegraph sync . && codegraph affected . +``` + +Expected: frontend verification and Go tests pass; whitespace check succeeds; CodeGraph is synchronized. + +- [ ] **Step 4: Checkpoint** + +Do not commit. Record the merge SHA and exact test results for the later final review. + +### Task 2: Add the authoritative Supabase migration and matching Ent schema + +**Objective:** Define one forward-only, minimal mapping table and make generated Ent code represent its exact columns and constraints. + +**Files:** +- Create: `supabase/config.toml` +- Create: `supabase/migrations/_create_internal_users.sql` +- Create: `backend/internal/users/migration_contract_test.go` +- Create: `backend/ent/entc.go` +- Create: `backend/ent/schema/internaluser.go` +- Create/generated: `backend/ent/**` +- Modify: `backend/go.mod`, `backend/go.sum` + +**Interfaces:** +- Consumes: the approved SQL contract `{ id uuid, clerk_subject text unique, created_at timestamptz, updated_at timestamptz }`. +- Produces: Ent type `InternalUser` with UUID ID, immutable unique subject, immutable creation time, and UTC update time. + +- [ ] **Step 1: Write the RED migration contract** + +Create `backend/internal/users/migration_contract_test.go` with an assertion that scans migration SQL and requires each of: + +```go +func TestCreateInternalUsersMigrationContract(t *testing.T) { + requireSQL(t, "create table public.internal_users") + requireSQL(t, "id uuid primary key") + requireSQL(t, "clerk_subject text not null unique") + requireSQL(t, "created_at timestamptz not null") + requireSQL(t, "updated_at timestamptz not null") + requireSQL(t, "char_length(clerk_subject) between 1 and 255") +} +``` + +Run: + +```bash +cd backend && go test ./internal/users -run TestCreateInternalUsersMigrationContract -count=1 +``` + +Expected: failure because the migration does not exist. + +- [ ] **Step 2: Add the minimal forward-only migration** + +Create exactly one timestamped `supabase/migrations/*_create_internal_users.sql` containing: + +```sql +create extension if not exists pgcrypto; + +create table public.internal_users ( + id uuid primary key, + clerk_subject text not null unique, + created_at timestamptz not null default timezone('utc', now()), + updated_at timestamptz not null default timezone('utc', now()), + constraint internal_users_clerk_subject_nonempty + check (char_length(clerk_subject) between 1 and 255) +); + +create index internal_users_created_at_idx + on public.internal_users (created_at); +``` + +Create Supabase config only through `supabase init` or an equivalent generated project config. Do not create a PostgreSQL service in `compose.yaml`. + +- [ ] **Step 3: Verify GREEN for the migration contract** + +Run: + +```bash +cd backend && go test ./internal/users -run TestCreateInternalUsersMigrationContract -count=1 +``` + +Expected: pass. + +- [ ] **Step 4: Write the RED Ent schema contract** + +Create `backend/ent/schema/internaluser_test.go` with static/schema assertions covering the exact `InternalUser` fields and no profile/email fields. Run: + +```bash +cd backend && go test ./ent/schema -run TestInternalUserSchemaContract -count=1 +``` + +Expected: failure because no Ent schema exists. + +- [ ] **Step 5: Add Ent dependencies, schema, and generator** + +Pin direct modules in `backend/go.mod`: + +```text +entgo.io/ent v0.14.6 +github.com/google/uuid +github.com/jackc/pgx/v5 v5.10.0 +``` + +Create `backend/ent/entc.go` with a `go:generate` directive that invokes Ent generation against `./schema`. Implement `InternalUser` so `id` uses `uuid.New`, `clerk_subject` is nonempty, max-255, unique, and immutable, `created_at` is immutable, and `updated_at` uses UTC time. Generate code with: + +```bash +cd backend && go generate ./ent +``` + +- [ ] **Step 6: Verify schema and generated output** + +Run: + +```bash +cd backend && go test ./ent/schema -run TestInternalUserSchemaContract -count=1 +go test ./... +go vet ./... +git diff --check +``` + +Expected: all pass. Inspect generated names to confirm `internal_users`, `clerk_subject`, `created_at`, and `updated_at` match the migration. + +- [ ] **Step 7: Checkpoint** + +Do not commit. Run a changed/untracked secret scan; report only paths and detection counts. + +### Task 3: Implement provider-neutral, race-safe reconciliation + +**Objective:** Reconcile a verified subject to a stable internal user without importing Clerk concepts into domain logic. + +**Files:** +- Create: `backend/internal/users/model.go` +- Create: `backend/internal/users/repository.go` +- Create: `backend/internal/users/service.go` +- Create: `backend/internal/users/ent_repository.go` +- Create: `backend/internal/users/service_test.go` +- Create: `backend/internal/users/ent_repository_test.go` + +**Interfaces:** + +```go +type VerifiedIdentity struct { Subject string } + +type InternalUser struct { + ID uuid.UUID + CreatedAt time.Time +} + +type Repository interface { + FindBySubject(context.Context, string) (InternalUser, bool, error) + Create(context.Context, string) (InternalUser, error) +} + +type Service interface { + Reconcile(context.Context, VerifiedIdentity) (InternalUser, bool, error) +} +``` + +- [ ] **Step 1: Write RED service cases** + +In `service_test.go`, use a recording fake repository and require: + +```go +func TestReconcileRejectsEmptySubjectBeforeRepositoryAccess(t *testing.T) +func TestReconcileCreatesFirstMapping(t *testing.T) +func TestReconcileReturnsExistingMappingWithoutMutation(t *testing.T) +func TestReconcileReloadsWinnerAfterUniqueConflict(t *testing.T) +func TestReconcileReturnsControlledFailureForRepositoryError(t *testing.T) +``` + +Run: + +```bash +cd backend && go test ./internal/users -run '^TestReconcile' -count=1 +``` + +Expected: failure because the service does not exist. + +- [ ] **Step 2: Implement the minimal service** + +Add only: + +```go +func (s service) Reconcile(ctx context.Context, identity VerifiedIdentity) (InternalUser, bool, error) +``` + +Validation occurs before `FindBySubject`. It returns an existing row with `false`; it creates a missing row with `true`; it handles only the repository’s typed unique-conflict error by finding the exact same subject once more and returning the winner with `false`. + +- [ ] **Step 3: Verify GREEN** + +Run the focused service command from Step 1. Expected: all named cases pass. + +- [ ] **Step 4: Write the RED Ent repository integration cases** + +Create database-backed tests that require: + +```go +func TestEntRepositoryFindsAndCreatesByExactSubject(t *testing.T) +func TestReconcileConcurrentSameSubjectProducesOneStableRow(t *testing.T) +``` + +The test harness must receive only a disposable `TEST_DATABASE_URL` through its environment and skip with an explicit message when absent. The final mandatory integration invocation must set that URL and must not treat a skipped test as proof. + +- [ ] **Step 5: Implement the Ent repository** + +Implement exact-subject query/create mapping. Translate only PostgreSQL/Ent uniqueness violations into the domain’s typed unique-conflict error. Do not retry arbitrary database errors and do not query by any display field or timestamp. + +- [ ] **Step 6: Verify against a disposable database** + +Start only the project-owned local Supabase stack, apply the entire migration ledger, supply the disposable test URL through environment, then run: + +```bash +cd backend && go test ./internal/users -run 'TestEntRepository|TestReconcileConcurrent' -count=1 +``` + +Expected: one mapping row, one stable UUID, and no duplicate mapping for a synthetic subject. Stop/reset the local stack afterwards without printing connection details. + +### Task 4: Verify bearer identity in Go before reconciliation + +**Objective:** Derive `VerifiedIdentity` only from a Clerk-verified session token and keep health publicly reachable. + +**Files:** +- Create: `backend/internal/authn/verifier.go` +- Create: `backend/internal/authn/clerk_verifier.go` +- Create: `backend/internal/authn/middleware.go` +- Create: `backend/internal/authn/middleware_test.go` +- Modify: `backend/go.mod`, `backend/go.sum` +- Modify: `backend/cmd/api/main.go` +- Modify: `backend/internal/httpapi/health_handler.go` + +**Interfaces:** + +```go +type Verifier interface { + Verify(context.Context, string) (users.VerifiedIdentity, error) +} + +func RequireVerifiedIdentity(verifier Verifier, next http.Handler) http.Handler +func IdentityFromContext(context.Context) (users.VerifiedIdentity, bool) +``` + +- [ ] **Step 1: Write RED middleware cases** + +Create tests for absent bearer header, malformed scheme, verifier rejection, empty verified subject, and valid subject context propagation. Add an assertion that the downstream handler is not invoked for every invalid case. + +Run: + +```bash +cd backend && go test ./internal/authn -run TestRequireVerifiedIdentity -count=1 +``` + +Expected: failure because the middleware package is absent. + +- [ ] **Step 2: Implement a testable verification boundary** + +Add the provider-neutral `Verifier` interface and middleware. The middleware parses only `Authorization: Bearer session-token`, invokes the verifier, rejects invalid outcomes with `401`, and stores a typed identity context value. It does not parse JWT claims itself. + +- [ ] **Step 3: Implement and compile-check the Clerk adapter** + +Add `github.com/clerk/clerk-sdk-go/v2@v2.7.0`. Use its documented session-verification middleware/API from a dedicated `clerk_verifier.go` adapter, configured only by `CLERK_SECRET_KEY`, optional `CLERK_JWT_KEY`, and exact `CLERK_AUTHORIZED_PARTIES`. Require an actual compile-backed adapter test; do not retain an uncompiled guessed method call. Convert Clerk failures to the local verifier error without embedding SDK diagnostics. + +- [ ] **Step 4: Verify GREEN** + +Run: + +```bash +cd backend && go test ./internal/authn -count=1 +go test ./... +go vet ./... +go build ./cmd/api +``` + +Expected: pass. Health remains public because authentication middleware will be applied only to the reconciliation route in Task 5. + +### Task 5: Add the closed reconciliation API contract and handler + +**Objective:** Add a body-less, authenticated API that returns only an opaque mapping response. + +**Files:** +- Modify: `openapi/juntly-api.v1.yaml` +- Modify/generated: `frontend/src/shared/api/generated/**` +- Create: `backend/internal/httpapi/reconcile_handler.go` +- Create: `backend/internal/httpapi/reconcile_handler_test.go` +- Modify: `backend/internal/httpapi/health_handler.go` + +**Interfaces:** + +```go +type ReconcileService interface { + Reconcile(context.Context, users.VerifiedIdentity) (users.InternalUser, bool, error) +} + +func NewReconcileHandler(service ReconcileService) http.Handler +``` + +- [ ] **Step 1: Write RED handler cases** + +Create tests requiring: + +```go +func TestReconcileHandlerReturnsUnauthorizedWithoutVerifiedIdentity(t *testing.T) +func TestReconcileHandlerReturnsOpaqueInternalUser(t *testing.T) +func TestReconcileHandlerReturnsSafeUnavailableForDependencyFailure(t *testing.T) +func TestReconcileHandlerRejectsNonPOSTMethods(t *testing.T) +``` + +The successful response must include only UUID `id`, RFC3339 `createdAt`, and the correlation header. The failure body must not include the synthetic repository error text. + +Run: + +```bash +cd backend && go test ./internal/httpapi -run TestReconcileHandler -count=1 +``` + +Expected: failure because the handler does not exist. + +- [ ] **Step 2: Add the OpenAPI contract** + +Add `POST /api/v1/auth/reconcile` with no request body, `clerkSession` HTTP bearer security, and exact responses: + +```yaml +200: InternalUserResponse +401: ErrorResponse with code UNAUTHORIZED +503: ErrorResponse with code SERVICE_UNAVAILABLE +``` + +`InternalUserResponse` has only `id: uuid` and `createdAt: date-time`. All write schemas remain closed; no caller-controlled identity field exists. + +- [ ] **Step 3: Implement handler and route composition** + +Implement `NewReconcileHandler` using `IdentityFromContext`, `ReconcileService`, and the existing correlation-header helper. Extend `NewRouter` to register the public health handler unchanged and wrap only `/api/v1/auth/reconcile` with `RequireVerifiedIdentity`. + +- [ ] **Step 4: Regenerate and verify GREEN** + +Run: + +```bash +npm --prefix frontend run codegen +npm --prefix frontend run codegen:check +cd backend && go test ./internal/httpapi -run TestReconcileHandler -count=1 +go test ./... +``` + +Expected: generated client includes the reconciliation endpoint and all focused/full Go tests pass. + +### Task 6: Add same-origin, Clerk-aware BFF reconciliation + +**Objective:** Reconcile an authenticated session without revealing Go topology or permitting browser-provided identity claims. + +**Files:** +- Create: `frontend/src/app/api/v1/auth/reconcile/route.ts` +- Create: `frontend/src/app/api/v1/auth/reconcile/route.test.ts` +- Modify: `frontend/.env.example` +- Modify: `frontend/README.md` +- Modify: `compose.yaml` + +**Interfaces:** + +```ts +export async function POST(request: Request): Promise +``` + +The route consumes `await auth()` from `@clerk/nextjs/server`, a request-local token from `getToken()`, `process.env.JUNTLY_API_ORIGIN`, and the generated OpenAPI SDK. It produces only `InternalUserResponse` or the documented safe error envelope. + +- [ ] **Step 1: Write RED BFF cases** + +Mock Clerk server auth and upstream fetch. Require: + +```ts +it("returns 401 without an authenticated Clerk session", async () => {}) +it("returns 401 when the authenticated session has no current token", async () => {}) +it("forwards only a server-obtained bearer token and correlation ID", async () => {}) +it("maps upstream failure to a topology-safe 503", async () => {}) +it("maps malformed upstream response to a topology-safe 503", async () => {}) +``` + +The success case asserts that request headers include `Authorization: Bearer session-token` and `X-Request-ID`, while the browser response body excludes both the bearer token and backend origin. + +Run: + +```bash +npm --prefix frontend test -- src/app/api/v1/auth/reconcile/route.test.ts +``` + +Expected: failure because the route is absent. + +- [ ] **Step 2: Implement the smallest BFF** + +Call `await auth()`. Return `401` before upstream access when not authenticated or no token is available. Forward a current server-obtained token and the validated/generated request ID only to `JUNTLY_API_ORIGIN`. Validate the generated client result structurally before returning its two response fields. Convert all upstream/configuration/validation exceptions to the documented generic `503` without leakable cause text. + +- [ ] **Step 3: Add only safe configuration documentation** + +Add variable names/placeholders—never values—to `.env.example`, `compose.yaml`, and the frontend README. Document `localhost:4200` as the local browser authority and `JUNTLY_API_ORIGIN` as server-only. + +- [ ] **Step 4: Verify GREEN** + +Run: + +```bash +npm --prefix frontend test -- src/app/api/v1/auth/reconcile/route.test.ts +npm --prefix frontend run verify +docker compose config +``` + +Expected: all pass with no public backend-origin value in generated browser assets or response fixtures. + +### Task 7: Perform durable, runtime, and contract verification + +**Objective:** Produce evidence for database durability, API safety, and local authenticated behavior without fabricating a user or exposing credentials. + +**Files:** +- Modify only if evidence changes instructions: `frontend/README.md`, `context/architecture.md`, `context/stack.md` +- Verify: all Task 1–6 paths, plus untracked documentation/spec/plan files. + +- [ ] **Step 1: Run migration-chain and concurrent integration proof** + +Use a disposable local Supabase project/database only. Apply all migrations in order. Run the Task 3 integration test with `TEST_DATABASE_URL` injected only through the process environment. Capture only pass/fail, synthetic row count, UUID equality, and timestamp equality; do not print connection strings or database credentials. + +- [ ] **Step 2: Run all local quality gates** + +Run: + +```bash +codegraph sync . +codegraph affected . +cd backend && go test ./... && go vet ./... && go build ./cmd/api +cd .. && npm --prefix frontend run verify +docker compose config +git diff --check +``` + +Expected: all commands pass. Any untracked source file is inspected separately because `git diff --check` does not cover it. + +- [ ] **Step 3: Run a secret-safe changed-file review** + +Inspect staged, modified, and untracked source/docs for credential-like values. Report only path names and detection counts. Confirm ignored local config remains ignored with `git check-ignore` using path names only. + +- [ ] **Step 4: Run the optional real-session smoke only when authorized** + +With an approved development Clerk test user, start the local API/frontend at canonical `localhost:4200`, establish a real session, and call same-origin `POST /api/v1/auth/reconcile` twice. Evidence is limited to statuses, request IDs, stable opaque-ID equality, stable creation-time equality, and synthetic-safe row count. Do not record email, subject, cookie, bearer, URL, or secret. + +If no approved test user/session is available, report this gate as blocked; do not claim it from unit tests. + +- [ ] **Step 5: Final review and delivery boundary** + +Create a requirement-to-code-to-test matrix against `docs/superpowers/specs/2026-08-21-durable-internal-user-mapping-design.md`. Re-run `git status --short --branch`, `git diff --check`, and inspect every untracked mapping file. Do not commit, push, merge, deploy, or update Kanban terminal status without separate user authorization. diff --git a/docs/superpowers/specs/2026-08-21-durable-internal-user-mapping-design.md b/docs/superpowers/specs/2026-08-21-durable-internal-user-mapping-design.md new file mode 100644 index 0000000..dceb9bb --- /dev/null +++ b/docs/superpowers/specs/2026-08-21-durable-internal-user-mapping-design.md @@ -0,0 +1,177 @@ +# Durable Internal User Mapping Design + +**Status:** Approved for implementation planning + +## Purpose + +Create a minimal, durable, provider-isolated mapping from a Clerk-verified session subject to one opaque internal Juntly user ID. This mapping is the required ownership primitive for later provider profiles, listings, orders, and other marketplace resources. + +## Scope + +This slice adds only: + +- project-owned Supabase PostgreSQL migration history; +- an Ent representation of one internal-user mapping; +- a Go reconciliation service and race-safe repository; +- Clerk bearer-token verification at the Go API boundary; +- an authenticated, same-origin Next.js BFF reconciliation endpoint; +- OpenAPI/client types and layered verification. + +This slice deliberately excludes profiles, display names, emails, roles, tiers, organisations, Clerk metadata writes, webhooks, listings, provider/customer ownership, payments, and marketplace data. + +## Branch and integration topology + +The implementation branch is `feature/internal-user-mapping`. + +It requires these immutable local predecessors: + +- `727f288` — health/API foundation, already integrated into local `development`. +- `e154f44` — localized Clerk identity foundation, currently on `feature/clerk-identity`. + +Before feature code starts, the mapping branch must consume both predecessors as a stacked local branch. The Clerk source must not be duplicated or recreated. The existing health endpoint remains public and unchanged. + +No remote branch update, pull request, deployment, remote Supabase migration, or Clerk resource/metadata mutation is in scope. + +## Trust and data flow + +```text +Authenticated browser + → same-origin Next.js BFF + → current Clerk bearer token (server obtained) + → Go reconciliation endpoint + → Clerk token verification + → verified Clerk subject + → reconciliation service + → Ent repository + → Supabase PostgreSQL internal_users row +``` + +### Authority rules + +1. The browser never sends a Clerk subject, user ID, role, email, profile field, or internal ID. +2. The BFF calls `await auth()` and obtains a current token only after session validation. +3. The BFF forwards the token only to server-only `JUNTLY_API_ORIGIN`. +4. Go verifies every bearer token independently through the pinned Clerk Go SDK and accepts only a valid session with an allowed authorized party. +5. The verified `sub` becomes a typed `VerifiedIdentity` value. No other Clerk claims enter the mapping domain. +6. The database contains no bearer token, session, raw provider payload, email, display name, or metadata. + +The canonical browser-facing local auth origin stays `http://localhost:4200`. + +## Database model + +A forward-only migration creates `public.internal_users`: + +```sql +id uuid primary key, +clerk_subject text not null unique, +created_at timestamptz not null, +updated_at timestamptz not null +``` + +Rules: + +- `id` is generated by trusted application code as a UUID; it is opaque and not caller-controlled. +- `clerk_subject` is a nonempty, bounded Clerk `sub`, unique and immutable after insertion. +- `created_at` is immutable. +- `updated_at` is application-owned; this slice does not synchronise a user profile. +- A subject lookup is indexed through the unique constraint. +- SQL under `supabase/migrations/` is the authoritative, ordered history. Ent schema/code must match it exactly. +- The API must not run automatic schema creation or migration at startup. + +## Reconciliation contract + +The provider-neutral application interface is: + +```go +type VerifiedIdentity struct { + Subject string +} + +type InternalUser struct { + ID uuid.UUID + CreatedAt time.Time +} + +type Repository interface { + FindBySubject(context.Context, string) (InternalUser, bool, error) + Create(context.Context, string) (InternalUser, error) +} + +type Service interface { + Reconcile(context.Context, VerifiedIdentity) (InternalUser, bool, error) +} +``` + +Behavior: + +1. Validate an empty or oversized subject before repository access. +2. Return an existing mapping unchanged with `created=false`. +3. Create one mapping for a previously unseen verified subject with `created=true`. +4. If concurrent requests collide on unique subject creation, reload the exact winning mapping and return it with `created=false`. +5. Map unknown persistence failures to a controlled dependency failure, never a successful reconciliation. + +## HTTP contract + +OpenAPI adds the body-less endpoint: + +```text +POST /api/v1/auth/reconcile +Authorization: Bearer session-token +``` + +Responses: + +| Status | Body | Meaning | +|---|---|---| +| `200` | `{ id: UUID, createdAt: RFC3339 timestamp }` | Existing or newly created mapping. The response intentionally does not reveal the Clerk subject. | +| `401` | Closed unauthenticated error envelope | Token absent, invalid, expired, malformed, or unauthorized party. | +| `503` | Existing privacy-safe service-unavailable envelope | Database or dependency failure. | + +The BFF defines an equivalent same-origin route. It accepts no browser body and returns only the documented mapping response. It converts upstream failures, malformed data, request-ID mismatches, missing backend origin, and token-forwarding failure into an allowlisted error envelope. It never emits an upstream URL, bearer token, subject, Clerk diagnostic, database diagnostic, or stack trace. + +## Configuration + +Tracked files contain variable names and safe placeholders only. + +Server-only runtime variable names: + +```text +JUNTLY_API_ORIGIN +DATABASE_URL +CLERK_SECRET_KEY +CLERK_JWT_KEY # optional networkless verification +CLERK_AUTHORIZED_PARTIES +``` + +Credentials, database URLs, generated Supabase keys, and token material remain only in ignored local configuration or process environments. They must not be read, printed, staged, committed, or copied into test fixtures. + +## Verification requirements + +Every behavior follows RED → GREEN → REFACTOR. + +1. Migration contract test: one table, UUID ID, immutable unique subject, timestamps, and lookup constraint/index. +2. Ent schema test: generated model maps exactly to migration semantics. +3. Service tests: invalid subject rejects before repository calls, first creation, repeat idempotency, unique-conflict winner reload, and controlled persistence failure. +4. Authentication middleware tests: absent/malformed token, verifier failure, missing subject, unauthorized party, and verified-subject context propagation. Tests use synthetic fakes, never real tokens. +5. Handler/OpenAPI tests: body-less closed endpoint, `401` before service invocation, documented `200`, and privacy-safe `503`. +6. BFF tests: signed-out rejection, missing token, valid safe forwarding, malformed upstream rejection, and no upstream topology leakage. +7. Disposable local Supabase/PostgreSQL integration: apply the complete migration chain; concurrently reconcile one synthetic subject; prove one row, one stable opaque UUID, stable creation time, and no duplicate mapping. +8. Real local browser proof is separate: with an approved development test user, call the same-origin BFF twice and record only HTTP status, request IDs, stable opaque-ID equality, creation-time equality, and row count. Do not record subject, email, cookie, bearer token, or database URL. + +Required local gates after implementation: + +```text +codegraph affected . +go test ./... +go vet ./... +go build ./cmd/api +npm --prefix frontend run verify +supabase migration-chain validation against disposable local state +docker compose config +git diff --check +staged/untracked secret scan +``` + +## Non-goals and follow-on boundary + +A successful mapping only proves durable identity reconciliation. It does not prove an authenticated browser session until a real approved test-user journey runs, and it does not authorize marketplace work by itself. Provider profiles, listings, ownership edges, payments, and entitlements start only in later approved slices after this mapping is durable and verified. diff --git a/frontend/README.md b/frontend/README.md index 653eb30..67d8aca 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -10,11 +10,12 @@ This foundation currently provides: - Localized metadata and route boundaries. - A responsive, accessible product-introduction shell. - A same-origin `/api/v1/health` BFF route backed by the generated OpenAPI client. +- A same-origin, Clerk-aware `POST /api/v1/auth/reconcile` BFF route that returns only an opaque internal mapping ID and creation time. - A localized client-side health indicator. - Clerk account entry, locale-aware in-app authentication forms, and a fail-closed session boundary at `/:locale/account`. - Test, format, lint, type, build, dependency-audit, and CI foundations. -Provider/customer profiles, Go internal-user mapping, listings, search, chat, quotations, bookings, payments, and external infrastructure are not implemented yet. The only Go endpoint is the foundation health tracer. +Provider/customer profiles, listings, search, chat, quotations, bookings, payments, and external infrastructure are not implemented yet. The Go API also has a protected internal-user reconciliation endpoint; authenticated browser proof requires an approved development test user and is recorded separately. ## Requirements @@ -65,8 +66,13 @@ For local frontend/API proof, run from the repository root: docker compose up --build ``` -The compose topology supplies the server-only BFF origin. For a native frontend -runtime, copy `.env.example` to an ignored `.env.local` and start the Go API on -`127.0.0.1:8080`. +The compose topology supplies the server-only BFF origin. The API requires +server-only `DATABASE_URL`, Clerk verification material (`CLERK_SECRET_KEY` or +`CLERK_JWT_KEY`), and exact `CLERK_AUTHORIZED_PARTIES`; copy +`backend/.env.example` to ignored backend runtime configuration or set the same +variables in the service environment. For a native frontend runtime, copy +`.env.example` to an ignored `.env.local` and start the Go API on +`127.0.0.1:8080`. Do not add a `NEXT_PUBLIC_` prefix to `JUNTLY_API_ORIGIN` or +any backend/Clerk credential variable. Project-wide architecture and product rules live in `../context/`. diff --git a/frontend/src/app/api/v1/auth/reconcile/route.test.ts b/frontend/src/app/api/v1/auth/reconcile/route.test.ts new file mode 100644 index 0000000..1830efe --- /dev/null +++ b/frontend/src/app/api/v1/auth/reconcile/route.test.ts @@ -0,0 +1,167 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + auth: vi.fn(), +})); + +vi.mock("@clerk/nextjs/server", () => ({ auth: mocks.auth })); + +import { POST } from "./route"; + +describe("POST /api/v1/auth/reconcile BFF route", () => { + beforeEach(() => { + vi.stubEnv("JUNTLY_API_ORIGIN", "http://go-api:8080"); + }); + + afterEach(() => { + mocks.auth.mockReset(); + vi.unstubAllEnvs(); + vi.restoreAllMocks(); + }); + + it("returns 401 without an authenticated Clerk session", async () => { + mocks.auth.mockResolvedValue({ isAuthenticated: false, getToken: vi.fn() }); + const upstreamFetch = vi.fn(); + vi.stubGlobal("fetch", upstreamFetch); + + const response = await POST( + new Request("http://localhost/api/v1/auth/reconcile", { + method: "POST", + headers: { "X-Request-ID": "req_reconcile_signed_out" }, + }), + ); + + expect(response.status).toBe(401); + await expect(response.json()).resolves.toEqual({ + error: { + code: "UNAUTHORIZED", + message: "Unauthorized", + requestId: "req_reconcile_signed_out", + }, + }); + expect(upstreamFetch).not.toHaveBeenCalled(); + }); + + it("returns 401 when an authenticated session has no current token", async () => { + mocks.auth.mockResolvedValue({ + isAuthenticated: true, + getToken: vi.fn().mockResolvedValue(null), + }); + const upstreamFetch = vi.fn(); + vi.stubGlobal("fetch", upstreamFetch); + + const response = await POST( + new Request("http://localhost/api/v1/auth/reconcile", { + method: "POST", + headers: { "X-Request-ID": "req_reconcile_no_token" }, + }), + ); + + expect(response.status).toBe(401); + expect(upstreamFetch).not.toHaveBeenCalled(); + }); + + it("forwards only a server-obtained bearer token and correlation ID", async () => { + const getToken = vi.fn().mockResolvedValue("server-obtained-token"); + mocks.auth.mockResolvedValue({ isAuthenticated: true, getToken }); + const upstreamFetch = vi.fn(async (request: Request) => { + expect(request.url).toBe("http://go-api:8080/api/v1/auth/reconcile"); + expect(request.method).toBe("POST"); + expect(request.headers.get("Authorization")).toBe( + "Bearer server-obtained-token", + ); + expect(request.headers.get("X-Request-ID")).toBe("req_reconcile_success"); + await expect(request.text()).resolves.toBe(""); + + return Response.json( + { + id: "7b7b7d7e-38f9-4f0c-8a10-0fce9cf6f82b", + createdAt: "2026-08-21T12:00:00.123456Z", + }, + { headers: { "X-Request-ID": "req_reconcile_success" } }, + ); + }); + vi.stubGlobal("fetch", upstreamFetch); + + const response = await POST( + new Request("http://localhost/api/v1/auth/reconcile", { + method: "POST", + headers: { "X-Request-ID": "req_reconcile_success" }, + }), + ); + + expect(response.status).toBe(200); + expect(response.headers.get("X-Request-ID")).toBe("req_reconcile_success"); + await expect(response.json()).resolves.toEqual({ + id: "7b7b7d7e-38f9-4f0c-8a10-0fce9cf6f82b", + createdAt: "2026-08-21T12:00:00.123456Z", + }); + expect(getToken).toHaveBeenCalledOnce(); + }); + + it("maps upstream failure to a topology-safe 503", async () => { + mocks.auth.mockResolvedValue({ + isAuthenticated: true, + getToken: vi.fn().mockResolvedValue("server-obtained-token"), + }); + vi.stubGlobal( + "fetch", + vi.fn(async () => { + throw new Error( + "connect ECONNREFUSED http://go-api:8080/api/v1/auth/reconcile", + ); + }), + ); + + const response = await POST( + new Request("http://localhost/api/v1/auth/reconcile", { + method: "POST", + headers: { "X-Request-ID": "req_reconcile_unavailable" }, + }), + ); + + expect(response.status).toBe(503); + const body = await response.json(); + expect(body).toEqual({ + error: { + code: "SERVICE_UNAVAILABLE", + message: "Service unavailable", + requestId: "req_reconcile_unavailable", + }, + }); + expect(JSON.stringify(body)).not.toContain("go-api"); + expect(JSON.stringify(body)).not.toContain("ECONNREFUSED"); + }); + + it("maps malformed upstream response to a topology-safe 503", async () => { + mocks.auth.mockResolvedValue({ + isAuthenticated: true, + getToken: vi.fn().mockResolvedValue("server-obtained-token"), + }); + vi.stubGlobal( + "fetch", + vi.fn(async () => + Response.json( + { id: "not-a-uuid", createdAt: "not-a-timestamp" }, + { headers: { "X-Request-ID": "req_reconcile_malformed" } }, + ), + ), + ); + + const response = await POST( + new Request("http://localhost/api/v1/auth/reconcile", { + method: "POST", + headers: { "X-Request-ID": "req_reconcile_malformed" }, + }), + ); + + expect(response.status).toBe(503); + await expect(response.json()).resolves.toEqual({ + error: { + code: "SERVICE_UNAVAILABLE", + message: "Service unavailable", + requestId: "req_reconcile_malformed", + }, + }); + }); +}); diff --git a/frontend/src/app/api/v1/auth/reconcile/route.ts b/frontend/src/app/api/v1/auth/reconcile/route.ts new file mode 100644 index 0000000..d9ddba8 --- /dev/null +++ b/frontend/src/app/api/v1/auth/reconcile/route.ts @@ -0,0 +1,128 @@ +import { auth } from "@clerk/nextjs/server"; + +import { reconcileInternalUser } from "@/shared/api/generated"; +import type { + ErrorResponse, + InternalUserResponse, +} from "@/shared/api/generated"; + +const requestIDHeader = "X-Request-ID"; +const unavailableMessage = "Service unavailable"; +const unauthorizedMessage = "Unauthorized"; + +export const runtime = "nodejs"; + +export async function POST(request: Request): Promise { + const requestID = readRequestID(request.headers); + const { isAuthenticated, getToken } = await auth(); + + if (!isAuthenticated) { + return unauthorizedResponse(requestID); + } + + const token = await getToken(); + if (!token) { + return unauthorizedResponse(requestID); + } + + const apiOrigin = process.env.JUNTLY_API_ORIGIN; + if (!apiOrigin) { + return unavailableResponse(requestID); + } + + try { + const upstream = await reconcileInternalUser({ + baseUrl: apiOrigin, + headers: { + Authorization: `Bearer ${token}`, + [requestIDHeader]: requestID, + }, + }); + + if ( + upstream.error || + !upstream.response?.ok || + upstream.response.headers.get(requestIDHeader) !== requestID || + !isInternalUserResponse(upstream.data) + ) { + return unavailableResponse(requestID); + } + + return Response.json(upstream.data, { + status: httpStatusOK, + headers: { + [requestIDHeader]: requestID, + }, + }); + } catch { + return unavailableResponse(requestID); + } +} + +const httpStatusOK = 200; + +function unauthorizedResponse(requestID: string): Response { + return errorResponse("UNAUTHORIZED", unauthorizedMessage, 401, requestID); +} + +function unavailableResponse(requestID: string): Response { + return errorResponse( + "SERVICE_UNAVAILABLE", + unavailableMessage, + 503, + requestID, + ); +} + +function errorResponse( + code: ErrorResponse["error"]["code"], + message: string, + status: number, + requestID: string, +): Response { + const body: ErrorResponse = { + error: { + code, + message, + requestId: requestID, + }, + }; + + return Response.json(body, { + status, + headers: { + [requestIDHeader]: requestID, + }, + }); +} + +function readRequestID(headers: Headers): string { + const requestID = headers.get(requestIDHeader); + + if (requestID && isValidRequestID(requestID)) { + return requestID; + } + + return `req_${globalThis.crypto.randomUUID()}`; +} + +function isValidRequestID(value: string): boolean { + return /^[A-Za-z0-9._:-]{8,128}$/.test(value); +} + +function isInternalUserResponse( + value: InternalUserResponse | undefined, +): value is InternalUserResponse { + return ( + typeof value?.id === "string" && + isUUID(value.id) && + typeof value.createdAt === "string" && + !Number.isNaN(Date.parse(value.createdAt)) + ); +} + +function isUUID(value: string): boolean { + return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( + value, + ); +} diff --git a/frontend/src/shared/api/generated/index.ts b/frontend/src/shared/api/generated/index.ts index 49050bf..bcccb74 100644 --- a/frontend/src/shared/api/generated/index.ts +++ b/frontend/src/shared/api/generated/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { getHealth, type Options } from './sdk.gen'; -export type { ClientOptions, ErrorCode, ErrorDetail, ErrorResponse, GetHealthData, GetHealthError, GetHealthErrors, GetHealthResponse, GetHealthResponses, HealthResponse, HealthStatus, RequestId, RequestIdHeader } from './types.gen'; +export { getHealth, type Options, reconcileInternalUser } from './sdk.gen'; +export type { ClientOptions, ErrorCode, ErrorDetail, ErrorResponse, GetHealthData, GetHealthError, GetHealthErrors, GetHealthResponse, GetHealthResponses, HealthResponse, HealthStatus, InternalUserResponse, ReconcileInternalUserData, ReconcileInternalUserError, ReconcileInternalUserErrors, ReconcileInternalUserResponse, ReconcileInternalUserResponses, RequestId, RequestIdHeader } from './types.gen'; diff --git a/frontend/src/shared/api/generated/sdk.gen.ts b/frontend/src/shared/api/generated/sdk.gen.ts index 018e288..89e0b60 100644 --- a/frontend/src/shared/api/generated/sdk.gen.ts +++ b/frontend/src/shared/api/generated/sdk.gen.ts @@ -2,7 +2,7 @@ import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; -import type { GetHealthData, GetHealthErrors, GetHealthResponses } from './types.gen'; +import type { GetHealthData, GetHealthErrors, GetHealthResponses, ReconcileInternalUserData, ReconcileInternalUserErrors, ReconcileInternalUserResponses } from './types.gen'; export type Options = Options2 & { /** @@ -24,3 +24,14 @@ export type Options(options?: Options) => (options?.client ?? client).get({ url: '/api/v1/health', ...options }); + +/** + * Reconcile the current verified session to an internal user. + * + * Accepts no request body and returns only opaque internal mapping data. + */ +export const reconcileInternalUser = (options?: Options) => (options?.client ?? client).post({ + security: [{ scheme: 'bearer', type: 'http' }], + url: '/api/v1/auth/reconcile', + ...options +}); diff --git a/frontend/src/shared/api/generated/types.gen.ts b/frontend/src/shared/api/generated/types.gen.ts index b8b0054..fc15678 100644 --- a/frontend/src/shared/api/generated/types.gen.ts +++ b/frontend/src/shared/api/generated/types.gen.ts @@ -16,11 +16,11 @@ export type HealthResponse = { requestId: RequestId; }; -export type ErrorCode = 'SERVICE_UNAVAILABLE'; +export type ErrorCode = 'UNAUTHORIZED' | 'SERVICE_UNAVAILABLE'; export type ErrorDetail = { code: ErrorCode; - message: 'Service unavailable'; + message: string; requestId: RequestId; }; @@ -28,6 +28,11 @@ export type ErrorResponse = { error: ErrorDetail; }; +export type InternalUserResponse = { + id: string; + createdAt: string; +}; + /** * Optional client-supplied correlation identifier. */ @@ -63,3 +68,38 @@ export type GetHealthResponses = { }; export type GetHealthResponse = GetHealthResponses[keyof GetHealthResponses]; + +export type ReconcileInternalUserData = { + body?: never; + headers?: { + /** + * Optional client-supplied correlation identifier. + */ + 'X-Request-ID'?: RequestId; + }; + path?: never; + query?: never; + url: '/api/v1/auth/reconcile'; +}; + +export type ReconcileInternalUserErrors = { + /** + * Session authorization is missing or invalid. + */ + 401: ErrorResponse; + /** + * A required service dependency is unavailable. + */ + 503: ErrorResponse; +}; + +export type ReconcileInternalUserError = ReconcileInternalUserErrors[keyof ReconcileInternalUserErrors]; + +export type ReconcileInternalUserResponses = { + /** + * Internal user mapping is available. + */ + 200: InternalUserResponse; +}; + +export type ReconcileInternalUserResponse = ReconcileInternalUserResponses[keyof ReconcileInternalUserResponses]; diff --git a/openapi/juntly-api.v1.yaml b/openapi/juntly-api.v1.yaml index 97c316d..1090e4c 100644 --- a/openapi/juntly-api.v1.yaml +++ b/openapi/juntly-api.v1.yaml @@ -33,7 +33,49 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorResponse" + /api/v1/auth/reconcile: + post: + operationId: reconcileInternalUser + summary: Reconcile the current verified session to an internal user. + description: Accepts no request body and returns only opaque internal mapping data. + security: + - clerkSession: [] + parameters: + - $ref: "#/components/parameters/RequestIdHeader" + responses: + "200": + description: Internal user mapping is available. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/InternalUserResponse" + "401": + description: Session authorization is missing or invalid. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "503": + description: A required service dependency is unavailable. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" components: + securitySchemes: + clerkSession: + type: http + scheme: bearer + bearerFormat: JWT parameters: RequestIdHeader: name: X-Request-ID @@ -87,6 +129,7 @@ components: ErrorCode: type: string enum: + - UNAUTHORIZED - SERVICE_UNAVAILABLE ErrorDetail: type: object @@ -100,7 +143,7 @@ components: $ref: "#/components/schemas/ErrorCode" message: type: string - const: Service unavailable + minLength: 1 requestId: $ref: "#/components/schemas/RequestId" ErrorResponse: @@ -111,3 +154,16 @@ components: properties: error: $ref: "#/components/schemas/ErrorDetail" + InternalUserResponse: + type: object + additionalProperties: false + required: + - id + - createdAt + properties: + id: + type: string + format: uuid + createdAt: + type: string + format: date-time diff --git a/supabase/.gitignore b/supabase/.gitignore new file mode 100644 index 0000000..ad9264f --- /dev/null +++ b/supabase/.gitignore @@ -0,0 +1,8 @@ +# Supabase +.branches +.temp + +# dotenvx +.env.keys +.env.local +.env.*.local diff --git a/supabase/config.toml b/supabase/config.toml new file mode 100644 index 0000000..70fa3a7 --- /dev/null +++ b/supabase/config.toml @@ -0,0 +1,388 @@ +# For detailed configuration reference documentation, visit: +# https://supabase.com/docs/guides/local-development/cli/config +# A string used to distinguish different Supabase projects on the same host. Defaults to the +# working directory name when running `supabase init`. +project_id = "juntly" + +[api] +enabled = true +# Port to use for the API URL. +port = 55421 +# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API +# endpoints. `public` and `graphql_public` schemas are included by default. +schemas = ["public", "graphql_public"] +# Extra schemas to add to the search_path of every request. +extra_search_path = ["public", "extensions"] +# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size +# for accidental or malicious requests. +max_rows = 1000 + +[api.tls] +# Enable HTTPS endpoints locally using a self-signed certificate. +enabled = false +# Paths to self-signed certificate pair. +# cert_path = "../certs/my-cert.pem" +# key_path = "../certs/my-key.pem" + +[db] +# Port to use for the local database URL. +port = 55422 +# Port used by db diff command to initialize the shadow database. +shadow_port = 55420 +# Maximum amount of time to wait for health check when starting the local database. +health_timeout = "2m" +# The database major version to use. This has to be the same as your remote database's. Run `SHOW +# server_version;` on the remote database to check. +major_version = 17 + +[db.pooler] +enabled = false +# Port to use for the local connection pooler. +port = 55429 +# Specifies when a server connection can be reused by other clients. +# Configure one of the supported pooler modes: `transaction`, `session`. +pool_mode = "transaction" +# How many server connections to allow per user/database pair. +default_pool_size = 20 +# Maximum number of client connections allowed. +max_client_conn = 100 + +# [db.vault] +# secret_key = "env(SECRET_VALUE)" + +[db.migrations] +# If disabled, migrations will be skipped during a db push or reset. +enabled = true +# Specifies an ordered list of schema files that describe your database. +# Supports glob patterns relative to supabase directory: "./schemas/*.sql" +schema_paths = [] + +[db.seed] +# If enabled, seeds the database after migrations during a db reset. +enabled = true +# Specifies an ordered list of seed files to load during db reset. +# Supports glob patterns relative to supabase directory: "./seeds/*.sql" +sql_paths = ["./seed.sql"] + +[db.network_restrictions] +# Enable management of network restrictions. +enabled = false +# List of IPv4 CIDR blocks allowed to connect to the database. +# Defaults to allow all IPv4 connections. Set empty array to block all IPs. +allowed_cidrs = ["0.0.0.0/0"] +# List of IPv6 CIDR blocks allowed to connect to the database. +# Defaults to allow all IPv6 connections. Set empty array to block all IPs. +allowed_cidrs_v6 = ["::/0"] + +# Uncomment to reject non-secure connections to the database. +# [db.ssl_enforcement] +# enabled = true + +[realtime] +enabled = true +# Bind realtime via either IPv4 or IPv6. (default: IPv4) +# ip_version = "IPv6" +# The maximum length in bytes of HTTP request headers. (default: 4096) +# max_header_length = 4096 + +[studio] +enabled = true +# Port to use for Supabase Studio. +port = 55423 +# External URL of the API server that frontend connects to. +api_url = "http://127.0.0.1" +# OpenAI API Key to use for Supabase AI in the Supabase Studio. +openai_api_key = "env(OPENAI_API_KEY)" + +# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they +# are monitored, and you can view the emails that would have been sent from the web interface. +[inbucket] +enabled = true +# Port to use for the email testing server web interface. +port = 55424 +# Uncomment to expose additional ports for testing user applications that send emails. +# smtp_port = 54325 +# pop3_port = 54326 +# admin_email = "admin@email.com" +# sender_name = "Admin" + +[storage] +enabled = true +# The maximum file size allowed (e.g. "5MB", "500KB"). +file_size_limit = "50MiB" + +# Uncomment to configure local storage buckets +# [storage.buckets.images] +# public = false +# file_size_limit = "50MiB" +# allowed_mime_types = ["image/png", "image/jpeg"] +# objects_path = "./images" + +# Allow connections via S3 compatible clients +[storage.s3_protocol] +enabled = true + +# Image transformation API is available to Supabase Pro plan. +# [storage.image_transformation] +# enabled = true + +# Store analytical data in S3 for running ETL jobs over Iceberg Catalog +# This feature is only available on the hosted platform. +[storage.analytics] +enabled = false +max_namespaces = 5 +max_tables = 10 +max_catalogs = 2 + +# Analytics Buckets is available to Supabase Pro plan. +# [storage.analytics.buckets.my-warehouse] + +# Store vector embeddings in S3 for large and durable datasets +# This feature is only available on the hosted platform. +[storage.vector] +enabled = false +max_buckets = 10 +max_indexes = 5 + +# Vector Buckets is available to Supabase Pro plan. +# [storage.vector.buckets.documents-openai] + +[auth] +enabled = true +# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used +# in emails. +site_url = "http://127.0.0.1:3000" +# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. +additional_redirect_urls = ["https://127.0.0.1:3000"] +# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). +jwt_expiry = 3600 +# JWT issuer URL. If not set, defaults to the local API URL (http://127.0.0.1:/auth/v1). +# jwt_issuer = "" +# Path to JWT signing key. DO NOT commit your signing keys file to git. +# signing_keys_path = "./signing_keys.json" +# If disabled, the refresh token will never expire. +enable_refresh_token_rotation = true +# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. +# Requires enable_refresh_token_rotation = true. +refresh_token_reuse_interval = 10 +# Allow/disallow new user signups to your project. +enable_signup = true +# Allow/disallow anonymous sign-ins to your project. +enable_anonymous_sign_ins = false +# Allow/disallow testing manual linking of accounts +enable_manual_linking = false +# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. +minimum_password_length = 6 +# Passwords that do not meet the following requirements will be rejected as weak. Supported values +# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols` +password_requirements = "" + +[auth.rate_limit] +# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled. +email_sent = 2 +# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled. +sms_sent = 30 +# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true. +anonymous_users = 30 +# Number of sessions that can be refreshed in a 5 minute interval per IP address. +token_refresh = 150 +# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users). +sign_in_sign_ups = 30 +# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address. +token_verifications = 30 +# Number of Web3 logins that can be made in a 5 minute interval per IP address. +web3 = 30 + +# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`. +# [auth.captcha] +# enabled = true +# provider = "hcaptcha" +# secret = "" + +[auth.email] +# Allow/disallow new user signups via email to your project. +enable_signup = true +# If enabled, a user will be required to confirm any email change on both the old, and new email +# addresses. If disabled, only the new email is required to confirm. +double_confirm_changes = true +# If enabled, users need to confirm their email address before signing in. +enable_confirmations = false +# If enabled, users will need to reauthenticate or have logged in recently to change their password. +secure_password_change = false +# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. +max_frequency = "1s" +# Number of characters used in the email OTP. +otp_length = 6 +# Number of seconds before the email OTP expires (defaults to 1 hour). +otp_expiry = 3600 + +# Use a production-ready SMTP server +# [auth.email.smtp] +# enabled = true +# host = "smtp.sendgrid.net" +# port = 587 +# user = "apikey" +# pass = "env(SENDGRID_API_KEY)" +# admin_email = "admin@email.com" +# sender_name = "Admin" + +# Uncomment to customize email template +# [auth.email.template.invite] +# subject = "You have been invited" +# content_path = "./supabase/templates/invite.html" + +# Uncomment to customize notification email template +# [auth.email.notification.password_changed] +# enabled = true +# subject = "Your password has been changed" +# content_path = "./templates/password_changed_notification.html" + +[auth.sms] +# Allow/disallow new user signups via SMS to your project. +enable_signup = false +# If enabled, users need to confirm their phone number before signing in. +enable_confirmations = false +# Template for sending OTP to users +template = "Your code is {{ .Code }}" +# Controls the minimum amount of time that must pass before sending another sms otp. +max_frequency = "5s" + +# Use pre-defined map of phone number to OTP for testing. +# [auth.sms.test_otp] +# 4152127777 = "123456" + +# Configure logged in session timeouts. +# [auth.sessions] +# Force log out after the specified duration. +# timebox = "24h" +# Force log out if the user has been inactive longer than the specified duration. +# inactivity_timeout = "8h" + +# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object. +# [auth.hook.before_user_created] +# enabled = true +# uri = "pg-functions://postgres/auth/before-user-created-hook" + +# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used. +# [auth.hook.custom_access_token] +# enabled = true +# uri = "pg-functions:////" + +# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. +[auth.sms.twilio] +enabled = false +account_sid = "" +message_service_sid = "" +# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: +auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" + +# Multi-factor-authentication is available to Supabase Pro plan. +[auth.mfa] +# Control how many MFA factors can be enrolled at once per user. +max_enrolled_factors = 10 + +# Control MFA via App Authenticator (TOTP) +[auth.mfa.totp] +enroll_enabled = false +verify_enabled = false + +# Configure MFA via Phone Messaging +[auth.mfa.phone] +enroll_enabled = false +verify_enabled = false +otp_length = 6 +template = "Your code is {{ .Code }}" +max_frequency = "5s" + +# Configure MFA via WebAuthn +# [auth.mfa.web_authn] +# enroll_enabled = true +# verify_enabled = true + +# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, +# `twitter`, `x`, `slack`, `spotify`, `workos`, `zoom`. +[auth.external.apple] +enabled = false +client_id = "" +# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: +secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" +# Overrides the default auth redirectUrl. +redirect_uri = "" +# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, +# or any other third-party OIDC providers. +url = "" +# If enabled, the nonce check will be skipped. Required for local sign in with Google auth. +skip_nonce_check = false +# If enabled, it will allow the user to successfully authenticate when the provider does not return an email address. +email_optional = false + +# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard. +# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting. +[auth.web3.solana] +enabled = false + +# Use Firebase Auth as a third-party provider alongside Supabase Auth. +[auth.third_party.firebase] +enabled = false +# project_id = "my-firebase-project" + +# Use Auth0 as a third-party provider alongside Supabase Auth. +[auth.third_party.auth0] +enabled = false +# tenant = "my-auth0-tenant" +# tenant_region = "us" + +# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth. +[auth.third_party.aws_cognito] +enabled = false +# user_pool_id = "my-user-pool-id" +# user_pool_region = "us-east-1" + +# Use Clerk as a third-party provider alongside Supabase Auth. +[auth.third_party.clerk] +enabled = false +# Obtain from https://clerk.com/setup/supabase +# domain = "example.clerk.accounts.dev" + +# OAuth server configuration +[auth.oauth_server] +# Enable OAuth server functionality +enabled = false +# Path for OAuth consent flow UI +authorization_url_path = "/oauth/consent" +# Allow dynamic client registration +allow_dynamic_registration = false + +[edge_runtime] +enabled = true +# Supported request policies: `oneshot`, `per_worker`. +# `per_worker` (default) — enables hot reload during local development. +# `oneshot` — fallback mode if hot reload causes issues (e.g. in large repos or with symlinks). +policy = "per_worker" +# Port to attach the Chrome inspector for debugging edge functions. +inspector_port = 5583 +# The Deno major version to use. +deno_version = 2 + +# [edge_runtime.secrets] +# secret_key = "env(SECRET_VALUE)" + +[analytics] +enabled = true +port = 55427 +# Configure one of the supported backends: `postgres`, `bigquery`. +backend = "postgres" + +# Experimental features may be deprecated any time +[experimental] +# Configures Postgres storage engine to use OrioleDB (S3) +orioledb_version = "" +# Configures S3 bucket URL, eg. .s3-.amazonaws.com +s3_host = "env(S3_HOST)" +# Configures S3 bucket region, eg. us-east-1 +s3_region = "env(S3_REGION)" +# Configures AWS_ACCESS_KEY_ID for S3 bucket +s3_access_key = "env(S3_ACCESS_KEY)" +# Configures AWS_SECRET_ACCESS_KEY for S3 bucket +s3_secret_key = "env(S3_SECRET_KEY)" diff --git a/supabase/migrations/20260821110514_create_internal_users.sql b/supabase/migrations/20260821110514_create_internal_users.sql new file mode 100644 index 0000000..7a2a738 --- /dev/null +++ b/supabase/migrations/20260821110514_create_internal_users.sql @@ -0,0 +1,14 @@ + +create extension if not exists pgcrypto; + +create table public.internal_users ( + id uuid primary key, + clerk_subject text not null unique, + created_at timestamptz not null default timezone('utc', now()), + updated_at timestamptz not null default timezone('utc', now()), + constraint internal_users_clerk_subject_nonempty + check (char_length(clerk_subject) between 1 and 255) +); + +create index internal_users_created_at_idx + on public.internal_users (created_at); From bf1b7e3053986dffba3f222c533cb925ab517118 Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 14:19:37 +0100 Subject: [PATCH 04/64] docs: define Juntly MVP vertical slices --- ...08-23-juntly-mvp-vertical-slices-design.md | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-23-juntly-mvp-vertical-slices-design.md diff --git a/docs/superpowers/specs/2026-08-23-juntly-mvp-vertical-slices-design.md b/docs/superpowers/specs/2026-08-23-juntly-mvp-vertical-slices-design.md new file mode 100644 index 0000000..0a165fd --- /dev/null +++ b/docs/superpowers/specs/2026-08-23-juntly-mvp-vertical-slices-design.md @@ -0,0 +1,136 @@ +# Juntly MVP Vertical Slices Design + +**Status:** Approved by SourceSensei on 2026-08-23 + +## Goal + +Deliver Juntly's Portugal-first marketplace MVP as twelve independently testable vertical slices after the Clerk-to-internal-user mapping foundation is proven in a real browser session. + +## Scope and non-goals + +Launch scope includes privacy-safe customer/provider accounts, configurable provider supply, public discovery, authenticated contact, chat, quotations, bookings, reviews, promotions, subscriptions, moderation, notifications, analytics, and launch readiness. + +This design excludes protected marketplace payments, payouts, refunds, payment dispute automation, MB WAY-compatible payments, advanced verification/analytics, native apps, and AI matching. Those are post-validation work and must not delay discovery, direct contact, chat, or quotations. + +## Foundation gate + +No marketplace-owned resource may be created before the existing identity foundation is proven end to end: + +```text +approved Clerk browser session +→ same-origin Next.js BFF +→ server-side Clerk token retrieval +→ Go verification and authorized-party enforcement +→ durable PostgreSQL internal-user reconciliation +``` + +The proof returns only status, correlation parity, and response-shape assertions; it never reveals a token, Clerk subject, or opaque internal-user UUID. + +## Shared architecture + +Every slice follows the same authority chain: + +```text +browser +→ localized Next.js Server/Client Components +→ same-origin BFF route +→ generated OpenAPI TypeScript client +→ Go REST transport +→ application service +→ Ent repository +→ project-owned Supabase PostgreSQL/PostGIS +``` + +- Clerk is identity authority; Go owns marketplace roles, ownership, state transitions, moderation, and persistence. +- Browser/UI state is never authorization. +- OpenAPI remains the public contract authority; generated TypeScript is regenerated for each contract update. +- SQL migrations are forward-only and API startup never migrates production schema. +- Every persisted lifecycle uses a unique owner relation, transactionally written audit event where appropriate, and compare-and-set transitions. +- Every browser-facing upstream failure is normalized to an allowlisted privacy-safe error response with correlation ID. + +## Cross-cutting requirements + +- Default locale is `pt-PT`; English ships; Spanish remains structurally supported. +- Public pages expose approximate location only and never raw contact details, exact addresses, private messages, proposals, booking data, or credentials. +- Categories, locations, currencies, fees, plans, promotion periods, languages, and feature flags are administrator-configured rather than hardcoded in UI components. +- Every user-owned resource has durable server-side ownership checks. +- All significant behavioral changes use RED → GREEN → REFACTOR with focused, full, integration, and browser evidence. +- Each slice is implemented in the canonical checkout by one writer, reviewed against a frozen candidate, committed locally, then integrated through `development` according to repository policy. + +## Delivery slices + +### Slice 1 — Onboarding and account capabilities + +Extend verified internal users with customer/provider/both capability state and privacy-safe onboarding data. It establishes server-owned role/capability checks and account settings without collecting payment, identity-document, or public contact data. + +**Acceptance evidence:** a verified internal user can complete safe onboarding; unauthorized users cannot read or mutate account state; locale-aware account routes work in browser and API tests. + +### Slice 2 — Taxonomy, locations, and provider profiles + +Add administrator-managed service categories/subcategories, Portugal-ready locality hierarchy, approximate provider location, service areas, travel radius, and provider profile fields. + +**Acceptance evidence:** administrators manage taxonomy; providers manage only their own profile/service areas; public responses omit precise address and private contact data; PostGIS/radius behavior is proven against disposable PostgreSQL. + +### Slice 3 — Listings, media, and moderation + +Add provider-owned listing drafts, review states, publishing/pausing/archiving, pricing/service-mode fields, and a safe storage adapter boundary for media. + +**Acceptance evidence:** invalid lifecycle transitions and cross-provider edits fail closed; draft/published/moderation visibility is proven; upload metadata never exposes private storage authority. + +### Slice 4 — Public discovery and search + +Add SEO-safe public listing/provider pages and proximity-aware discovery by category, text, approximate location, radius, language, availability, and price type. + +**Acceptance evidence:** low-bandwidth/mobile browser journey finds relevant nearby published listings; private contact/location fields are absent from public HTML and metadata; sponsored ranking cannot override relevance/trust completely. + +### Slice 5 — Contact reveal and lead events + +Add authenticated, provider-controlled phone/WhatsApp reveal with abuse controls, rate limiting, consent/configuration checks, and durable lead events. + +**Acceptance evidence:** public responses never contain contact values; eligible authenticated requests create one authorized lead event; repeated/abusive requests are controlled; direct external arrangements remain commission-free. + +### Slice 6 — Internal chat and notifications + +Add participant-authorized conversations/messages, blocking/reporting, bounded attachment metadata, unread state, and in-app/email notification adapters. + +**Acceptance evidence:** only conversation participants and authorized moderators access data; messages and reports remain private; notification preferences are honored; sensitive message/attachment contents are excluded from logs. + +### Slice 7 — Quotation requests and proposals + +Add quotation requests, provider eligibility/matching, private proposals, customer comparison, acceptance/rejection/expiry, and relevant notifications. + +**Acceptance evidence:** competitors cannot retrieve each other's proposals; matching is based on server-owned status/category/location/availability; one accepted proposal has one durable outcome. + +### Slice 8 — Booking state machine + +Add bookings from listings, accepted proposals, or direct agreements with server-authoritative transitions, idempotency, cancellation, and dispute foundations. + +**Acceptance evidence:** transitions are compare-and-set and audit-backed; duplicates do not create duplicate bookings/events; private service locations remain inaccessible to non-participants. + +### Slice 9 — Reviews and reputation + +Add reviews only for completed eligible bookings, provider responses, moderation, and rating aggregates. + +**Acceptance evidence:** self-reviews, duplicate booking reviews, and reviews without eligible interaction are rejected; verified-booking status is derived from durable booking state. + +### Slice 10 — Promotions and subscriptions + +Add administrator-configured professional plans and promotion windows behind server-owned entitlements. Essential marketplace participation remains free. + +**Acceptance evidence:** promotions are labelled and bounded; subscription checks do not grant admin or ownership; fee/plan literals are configuration-driven. + +### Slice 11 — Administration, moderation, and analytics + +Add administrator role enforcement, moderation workflows, audit records, notification preferences, and consent-aware operational metrics. + +**Acceptance evidence:** privileged operations are server-authorized and audited; dashboards expose bounded aggregate data; sensitive data never reaches public analytics or logs. + +### Slice 12 — Launch hardening and operations + +Complete privacy/GDPR controls, accessibility, performance, backup/restore, monitoring, incident handling, Docker/staging deployment, and real end-to-end acceptance journeys. + +**Acceptance evidence:** relevant security, accessibility, mobile/slow-network, migration, backup/restore, observability, Docker, HTTP, and browser gates pass; the same immutable artifact is promoted through `development → staging → main` only with owner approval. + +## Completion policy + +Each slice may finish only after its own contract matrix, focused tests, disposable PostgreSQL proof where persistence changes, full project gates, frozen review, and real runtime/browser evidence pass. A later slice cannot be used to claim an earlier slice is complete. From 80992c970cf70fdad415021e07bc75cd5810804e Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 14:23:14 +0100 Subject: [PATCH 05/64] docs: plan account capability onboarding --- .../2026-08-23-user-account-capabilities.md | 383 ++++++++++++++++++ 1 file changed, 383 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-23-user-account-capabilities.md diff --git a/docs/superpowers/plans/2026-08-23-user-account-capabilities.md b/docs/superpowers/plans/2026-08-23-user-account-capabilities.md new file mode 100644 index 0000000..7289f6b --- /dev/null +++ b/docs/superpowers/plans/2026-08-23-user-account-capabilities.md @@ -0,0 +1,383 @@ +# User Account Capabilities Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Give every verified Juntly internal user an implicit customer capability and an explicit, durable provider capability that can be read and changed only through authenticated same-origin API paths. + +**Architecture:** Keep `internal_users` limited to the immutable Clerk-subject mapping. Add a one-to-one `user_accounts` record keyed by `internal_user_id`; it carries only the provider capability and onboarding completion timestamp. Go reconciles the verified identity before account operations, enforces strict request schemas, and owns persistence. Next.js calls the Go API only through a same-origin BFF, then renders a localized account capability card. + +**Tech Stack:** Go, Ent, project-owned Supabase PostgreSQL, OpenAPI 3.1, generated TypeScript client, Next.js App Router, Clerk server auth, Vitest, Testing Library. + +## Global Constraints + +- Do not begin code work until `clerk-authenticated-browser-proof` has passed and the mapping foundation is promoted according to branch policy. +- Customer capability is implicit for every verified internal user; it is not persisted as a mutable role. +- `providerEnabled` is the sole Slice 1 mutable capability. Admin/moderator authority, public provider data, contact values, payment fields, and marketplace profiles are out of scope. +- Persist no Clerk subject, token, email, phone, exact location, profile text, contact data, or session material in `user_accounts`. +- Browser requests remain same-origin `/api/v1/...`; `JUNTLY_API_ORIGIN` stays server-only. +- OpenAPI schemas are closed at every boundary: unknown BFF/Go request fields are rejected before service invocation. +- `internal_users` remains the authoritative Clerk-subject mapping and migration history stays forward-only. +- Every behavioral unit follows RED → GREEN → REFACTOR and is locally committed only after its focused evidence/review gate. + +--- + +## File map + +| Path | Responsibility | +|---|---| +| `supabase/migrations/_create_user_accounts.sql` | Forward-only durable one-to-one capability table. | +| `backend/ent/schema/useraccount.go` | Ent schema for the capability record. | +| `backend/ent/**` | Generated Ent model/query/mutation code. | +| `backend/internal/accounts/model.go` | Capability domain DTOs and validation errors. | +| `backend/internal/accounts/repository.go` | Repository contract and controlled errors. | +| `backend/internal/accounts/ent_repository.go` | Ent persistence adapter. | +| `backend/internal/accounts/service.go` | Identity reconciliation plus account read/update service. | +| `backend/internal/accounts/*_test.go` | Unit and disposable PostgreSQL behavior proofs. | +| `backend/internal/httpapi/account_handler.go` | Protected GET/PUT account transport and strict JSON decoding. | +| `backend/internal/httpapi/account_handler_test.go` | HTTP schema/auth/correlation tests. | +| `backend/internal/httpapi/router_test.go` | Router composition regression. | +| `backend/internal/httpapi/openapi_contract_test.go` | OpenAPI endpoint/schema regression. | +| `backend/cmd/api/main.go` | Inject account service into the router. | +| `openapi/juntly-api.v1.yaml` | Closed public account contract. | +| `frontend/src/app/api/v1/me/account/route.ts` | Clerk-aware same-origin BFF GET/PUT bridge. | +| `frontend/src/app/api/v1/me/account/route.test.ts` | BFF auth, strict body, upstream, and privacy tests. | +| `frontend/src/features/account/account-capabilities-card.tsx` | Localized interactive capability UI. | +| `frontend/src/features/account/account-capabilities-card.test.tsx` | Client UI loading/update/error behavior tests. | +| `frontend/src/app/[locale]/account/page.tsx` | Hosts the capability card after server session enforcement. | +| `frontend/src/app/[locale]/account/page.test.tsx` | Protected account page composition regression. | +| `frontend/messages/pt-PT.json`, `frontend/messages/en.json`, `frontend/messages/es.json` | Account capability UI strings. | + +## Public contract + +```yaml +GET /api/v1/me/account + 200: AccountCapabilitiesResponse + 401: ErrorResponse + 503: ErrorResponse + +PUT /api/v1/me/account + requestBody: UpdateAccountCapabilitiesRequest + 200: AccountCapabilitiesResponse + 400: ErrorResponse + 401: ErrorResponse + 503: ErrorResponse + +AccountCapabilitiesResponse: + additionalProperties: false + required: [customerEnabled, providerEnabled, onboardingCompletedAt] + properties: + customerEnabled: { type: boolean, const: true } + providerEnabled: { type: boolean } + onboardingCompletedAt: { type: string, format: date-time } + +UpdateAccountCapabilitiesRequest: + additionalProperties: false + required: [providerEnabled] + properties: + providerEnabled: { type: boolean } +``` + +`onboardingCompletedAt` is set once, on the first successful GET or PUT that creates the account record, and remains stable. The account record is created lazily and idempotently after the identity is reconciled. No caller can provide an internal-user ID or Clerk subject. + +## Task 0: Foundation gate + +**Objective:** Establish the identity prerequisite before any Slice 1 source change. + +- [ ] Obtain real browser proof for `POST /api/v1/auth/reconcile` using an approved Clerk test user. +- [ ] Record only sanitized `200`, correlation parity, and opaque UUID/timestamp-shape evidence. +- [ ] Review and promote the mapping foundation through the repository branch policy. +- [ ] Create the Slice 1 feature branch from the approved `development` base in the canonical checkout; do not create a worktree or duplicate checkout. + +## Task 1: Add the durable account capability schema + +**Files:** +- Create: `supabase/migrations/_create_user_accounts.sql` +- Create: `backend/ent/schema/useraccount.go` +- Modify: `backend/ent/entc.go` +- Regenerate: `backend/ent/**` +- Create: `backend/ent/schema/useraccount_test.go` +- Modify: `backend/internal/users/migration_contract_test.go` + +**Interfaces:** + +```go +type UserAccount struct { + InternalUserID uuid.UUID + ProviderEnabled bool + OnboardingCompletedAt time.Time + CreatedAt time.Time + UpdatedAt time.Time +} +``` + +- [ ] **Step 1: Write failing migration/schema tests** + +Assert a `user_accounts` table with `internal_user_id uuid primary key references public.internal_users(id) on delete cascade`, `provider_enabled boolean not null default false`, non-null UTC `onboarding_completed_at`, creation/update timestamps, and no identity/contact columns. + +- [ ] **Step 2: Run RED checks** + +Run: + +```bash +cd backend && go test ./ent/schema ./internal/users -run 'Test.*UserAccount|Test.*Migration' -count=1 +``` + +Expected: failure because the `UserAccount` schema/migration does not exist. + +- [ ] **Step 3: Add the forward-only SQL migration and Ent schema** + +Use a UUID primary key relation to `internal_users`; do not add a redundant subject/email field. Set `provider_enabled` default false and immutable `internal_user_id`; use UTC time defaults. + +- [ ] **Step 4: Regenerate Ent and update the migration contract** + +Run the repository-supported Ent generator. Extend the SQL migration contract test to assert table/column/FK/default semantics rather than only migration file existence. + +- [ ] **Step 5: Run GREEN checks** + +Run the Task 1 command and confirm the schema/migration tests pass. + +- [ ] **Step 6: Commit** + +```bash +git add supabase/migrations backend/ent backend/internal/users/migration_contract_test.go +git commit -m "feat: add user account capability schema" +``` + +## Task 2: Build race-safe account service and repository + +**Files:** +- Create: `backend/internal/accounts/model.go` +- Create: `backend/internal/accounts/repository.go` +- Create: `backend/internal/accounts/ent_repository.go` +- Create: `backend/internal/accounts/service.go` +- Create: `backend/internal/accounts/service_test.go` +- Create: `backend/internal/accounts/ent_repository_test.go` + +**Interfaces:** + +```go +type Account struct { + CustomerEnabled bool + ProviderEnabled bool + OnboardingCompletedAt time.Time +} + +type Service interface { + Get(context.Context, users.VerifiedIdentity) (Account, error) + SetProviderEnabled(context.Context, users.VerifiedIdentity, bool) (Account, error) +} +``` + +- [ ] **Step 1: Write failing service tests** + +Cover: invalid verified identity; first `Get` reconciles identity and creates one account with `CustomerEnabled=true`, `ProviderEnabled=false`; first update enables provider; later update disables it; unknown repository failure maps to controlled unavailability; concurrent first reads/updates produce one account and stable onboarding timestamp. + +- [ ] **Step 2: Run RED checks** + +Run: + +```bash +cd backend && go test ./internal/accounts -run 'TestService' -count=1 +``` + +Expected: failure because `internal/accounts` does not exist. + +- [ ] **Step 3: Implement the minimal domain/repository/service layer** + +The account service first calls the existing internal-user reconciliation service using only `users.VerifiedIdentity`. The Ent repository finds or inserts a record keyed by internal-user ID; unique conflicts reload the winner. Normalize timestamps to UTC microsecond precision at the persistence boundary. + +- [ ] **Step 4: Add disposable PostgreSQL integration tests** + +Require `TEST_DATABASE_URL`; use synthetic UUID/subjects; scope cleanup to created rows; prove exact one-to-one account row and concurrent winner reload against the migration-applied local Supabase database. + +- [ ] **Step 5: Run GREEN checks** + +Run: + +```bash +cd backend && go test ./internal/accounts -count=1 +TEST_DATABASE_URL="$DB_URL" go test ./internal/accounts -run 'TestEntRepository|TestConcurrent' -count=1 +``` + +Expected: both pass; integration must not skip. + +- [ ] **Step 6: Commit** + +```bash +git add backend/internal/accounts +git commit -m "feat: add account capability service" +``` + +## Task 3: Publish the protected Go account API contract + +**Files:** +- Modify: `openapi/juntly-api.v1.yaml` +- Create: `backend/internal/httpapi/account_handler.go` +- Create: `backend/internal/httpapi/account_handler_test.go` +- Modify: `backend/internal/httpapi/health_handler.go` +- Modify: `backend/internal/httpapi/router_test.go` +- Modify: `backend/internal/httpapi/openapi_contract_test.go` +- Modify: `backend/cmd/api/main.go` + +**Interfaces:** + +```go +type AccountService interface { + Get(context.Context, users.VerifiedIdentity) (accounts.Account, error) + SetProviderEnabled(context.Context, users.VerifiedIdentity, bool) (accounts.Account, error) +} +``` + +- [ ] **Step 1: Write failing HTTP/OpenAPI tests** + +Assert: unauthenticated GET/PUT return closed JSON `401`; GET returns no identity values; PUT accepts exactly `{"providerEnabled": true|false}`; `{}`, `null`, unknown properties, non-boolean values, trailing JSON, and extra JSON values return closed JSON `400` without service invocation; all responses preserve valid/generated request IDs. + +- [ ] **Step 2: Run RED checks** + +Run: + +```bash +cd backend && go test ./internal/httpapi -run 'Test.*Account|Test.*OpenAPI' -count=1 +``` + +Expected: failure because account routes and schemas are absent. + +- [ ] **Step 3: Implement the closed API boundary** + +Add the two bearer-protected operations to OpenAPI. Use Go `json.Decoder` with `DisallowUnknownFields()` and explicit end-of-input validation. Reuse only allowlisted error envelopes; map controlled account failures to `503` and invalid payloads to a documented `400` error code added to the closed contract. + +- [ ] **Step 4: Generate and verify TypeScript client** + +Run: + +```bash +cd frontend && npm run codegen && npm run codegen:check +``` + +- [ ] **Step 5: Run GREEN checks** + +Run the Task 3 backend test command and `npm run codegen:check`; both pass. + +- [ ] **Step 6: Commit** + +```bash +git add openapi backend/internal/httpapi backend/cmd/api frontend/src/shared/api/generated +git commit -m "feat: add protected account capability API" +``` + +## Task 4: Add same-origin account BFF routes + +**Files:** +- Create: `frontend/src/app/api/v1/me/account/route.ts` +- Create: `frontend/src/app/api/v1/me/account/route.test.ts` + +- [ ] **Step 1: Write failing BFF tests** + +Cover GET and PUT signed-out responses (`401`, no upstream call); server-obtained bearer forwarding; valid response/correlation pass-through; strict browser JSON rejection before upstream; missing upstream origin, upstream errors, malformed data, and correlation mismatch mapping to topology-safe `503` without host/token text. + +- [ ] **Step 2: Run RED checks** + +Run: + +```bash +cd frontend && npm test -- src/app/api/v1/me/account/route.test.ts +``` + +Expected: failure because the account BFF route does not exist. + +- [ ] **Step 3: Implement GET and PUT BFF handlers** + +Use server-side `auth()` and `getToken()` only. Validate the browser request as an exact object with the sole `providerEnabled` boolean key before calling the generated client. Forward only the bearer and correlation ID upstream. Never expose `JUNTLY_API_ORIGIN`, Clerk details, or upstream topology. + +- [ ] **Step 4: Run GREEN checks** + +Run the Task 4 command and confirm all account BFF tests pass. + +- [ ] **Step 5: Commit** + +```bash +git add frontend/src/app/api/v1/me/account + git commit -m "feat: add same-origin account capability BFF" +``` + +## Task 5: Render localized account capability controls + +**Files:** +- Create: `frontend/src/features/account/account-capabilities-card.tsx` +- Create: `frontend/src/features/account/account-capabilities-card.test.tsx` +- Modify: `frontend/src/app/[locale]/account/page.tsx` +- Modify: `frontend/src/app/[locale]/account/page.test.tsx` +- Modify: `frontend/messages/pt-PT.json` +- Modify: `frontend/messages/en.json` +- Modify: `frontend/messages/es.json` +- Modify: `frontend/src/i18n/messages.test.ts` + +- [ ] **Step 1: Write failing component/page/i18n tests** + +Cover: loading state; fetched implicit customer capability; provider switch update; disabled controls while saving; controlled error state; no internal ID or provider token rendered; page still enforces `requireAuthenticatedUser(locale)`; exact message keys exist in all three locale files. + +- [ ] **Step 2: Run RED checks** + +Run: + +```bash +cd frontend && npm test -- src/features/account/account-capabilities-card.test.tsx src/app/[locale]/account/page.test.tsx src/i18n/messages.test.ts +``` + +Expected: failure because the capability card and messages do not exist. + +- [ ] **Step 3: Implement minimal accessible UI** + +Render an account-only card with semantic status text and one labelled provider-capability toggle. Use the same-origin BFF only; capture the local update request identity before awaiting and ignore stale completions. Keep browser-visible copy in locale JSON; do not display Clerk subject, internal UUID, email, contact data, or marketplace profile controls. + +- [ ] **Step 4: Run GREEN checks** + +Run the Task 5 command and confirm all focused tests pass. + +- [ ] **Step 5: Commit** + +```bash +git add frontend/src/features/account frontend/src/app/[locale]/account frontend/messages frontend/src/i18n/messages.test.ts +git commit -m "feat: add account capability onboarding UI" +``` + +## Task 6: Perform end-to-end Slice 1 acceptance + +**Files:** +- Modify only if evidence exposes a verified defect; otherwise none. + +- [ ] **Step 1: Apply the forward migration to disposable local Supabase** + +Use the project-owned local Supabase workflow. Keep database URLs/process credentials in environment only and never print them. + +- [ ] **Step 2: Run backend gates** + +```bash +cd backend && go test ./... && go vet ./... && go build -o "$LOCALAPPDATA/Temp/juntly-api.exe" ./cmd/api +``` + +- [ ] **Step 3: Run frontend gates** + +```bash +cd frontend && npm run verify +``` + +- [ ] **Step 4: Run topology and whitespace gates** + +```bash +cd .. && docker compose config >/dev/null && git diff --check && git diff --cached --check +``` + +- [ ] **Step 5: Run live runtime/browser proof** + +Start the Go API against disposable Supabase and start Next.js with a server-only local API origin. With an approved real Clerk session, prove browser GET and provider-capability PUT through the same-origin BFF, then repeat GET to prove durable state. Record only response status, correlation parity, `customerEnabled=true`, provider boolean state, and timestamp-shape assertions. + +- [ ] **Step 6: Freeze/review/deliver the slice** + +Synchronize CodeGraph, freeze the intended path set, scan staged paths for secrets, request a read-only contract/security review, commit the frozen candidate locally, and update the slice task only after every acceptance gate passes. + +## Risks and decisions + +- The current Clerk browser proof is operationally blocked by local session clock skew/preview-session availability. Slice 1 code must not be started until that proof is genuinely green. +- Provider capability is intentionally a narrow boolean. Public provider data, service areas, verification, listings, and contact controls begin in later slices. +- No external email/storage provider is selected in Slice 1. Notification/media adapters start only in their respective slices. From 82120c4c90d86ee860651bc1d1695bc4b308933d Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 14:47:04 +0100 Subject: [PATCH 06/64] feat: add user account capability schema --- backend/ent/client.go | 147 ++++- backend/ent/ent.go | 2 + backend/ent/hook/hook.go | 12 + backend/ent/migrate/schema.go | 18 + backend/ent/mutation.go | 496 ++++++++++++++++ backend/ent/predicate/predicate.go | 3 + backend/ent/runtime.go | 21 + backend/ent/schema/useraccount.go | 29 + backend/ent/schema/useraccount_test.go | 48 ++ backend/ent/tx.go | 3 + backend/ent/useraccount.go | 140 +++++ backend/ent/useraccount/useraccount.go | 86 +++ backend/ent/useraccount/where.go | 221 ++++++++ backend/ent/useraccount_create.go | 289 ++++++++++ backend/ent/useraccount_delete.go | 88 +++ backend/ent/useraccount_query.go | 528 ++++++++++++++++++ backend/ent/useraccount_update.go | 246 ++++++++ backend/go.sum | 32 ++ .../internal/users/migration_contract_test.go | 55 ++ .../20260823143901_create_user_accounts.sql | 7 + 20 files changed, 2469 insertions(+), 2 deletions(-) create mode 100644 backend/ent/schema/useraccount.go create mode 100644 backend/ent/schema/useraccount_test.go create mode 100644 backend/ent/useraccount.go create mode 100644 backend/ent/useraccount/useraccount.go create mode 100644 backend/ent/useraccount/where.go create mode 100644 backend/ent/useraccount_create.go create mode 100644 backend/ent/useraccount_delete.go create mode 100644 backend/ent/useraccount_query.go create mode 100644 backend/ent/useraccount_update.go create mode 100644 supabase/migrations/20260823143901_create_user_accounts.sql diff --git a/backend/ent/client.go b/backend/ent/client.go index 39bc21a..58fc681 100644 --- a/backend/ent/client.go +++ b/backend/ent/client.go @@ -16,6 +16,7 @@ import ( "entgo.io/ent/dialect" "entgo.io/ent/dialect/sql" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" ) // Client is the client that holds all ent builders. @@ -25,6 +26,8 @@ type Client struct { Schema *migrate.Schema // InternalUser is the client for interacting with the InternalUser builders. InternalUser *InternalUserClient + // UserAccount is the client for interacting with the UserAccount builders. + UserAccount *UserAccountClient } // NewClient creates a new client configured with the given options. @@ -37,6 +40,7 @@ func NewClient(opts ...Option) *Client { func (c *Client) init() { c.Schema = migrate.NewSchema(c.driver) c.InternalUser = NewInternalUserClient(c.config) + c.UserAccount = NewUserAccountClient(c.config) } type ( @@ -130,6 +134,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { ctx: ctx, config: cfg, InternalUser: NewInternalUserClient(cfg), + UserAccount: NewUserAccountClient(cfg), }, nil } @@ -150,6 +155,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) ctx: ctx, config: cfg, InternalUser: NewInternalUserClient(cfg), + UserAccount: NewUserAccountClient(cfg), }, nil } @@ -179,12 +185,14 @@ func (c *Client) Close() error { // In order to add hooks to a specific client, call: `client.Node.Use(...)`. func (c *Client) Use(hooks ...Hook) { c.InternalUser.Use(hooks...) + c.UserAccount.Use(hooks...) } // Intercept adds the query interceptors to all the entity clients. // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. func (c *Client) Intercept(interceptors ...Interceptor) { c.InternalUser.Intercept(interceptors...) + c.UserAccount.Intercept(interceptors...) } // Mutate implements the ent.Mutator interface. @@ -192,6 +200,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { switch m := m.(type) { case *InternalUserMutation: return c.InternalUser.mutate(ctx, m) + case *UserAccountMutation: + return c.UserAccount.mutate(ctx, m) default: return nil, fmt.Errorf("ent: unknown mutation type %T", m) } @@ -330,12 +340,145 @@ func (c *InternalUserClient) mutate(ctx context.Context, m *InternalUserMutation } } +// UserAccountClient is a client for the UserAccount schema. +type UserAccountClient struct { + config +} + +// NewUserAccountClient returns a client for the UserAccount from the given config. +func NewUserAccountClient(c config) *UserAccountClient { + return &UserAccountClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `useraccount.Hooks(f(g(h())))`. +func (c *UserAccountClient) Use(hooks ...Hook) { + c.hooks.UserAccount = append(c.hooks.UserAccount, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `useraccount.Intercept(f(g(h())))`. +func (c *UserAccountClient) Intercept(interceptors ...Interceptor) { + c.inters.UserAccount = append(c.inters.UserAccount, interceptors...) +} + +// Create returns a builder for creating a UserAccount entity. +func (c *UserAccountClient) Create() *UserAccountCreate { + mutation := newUserAccountMutation(c.config, OpCreate) + return &UserAccountCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of UserAccount entities. +func (c *UserAccountClient) CreateBulk(builders ...*UserAccountCreate) *UserAccountCreateBulk { + return &UserAccountCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *UserAccountClient) MapCreateBulk(slice any, setFunc func(*UserAccountCreate, int)) *UserAccountCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &UserAccountCreateBulk{err: fmt.Errorf("calling to UserAccountClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*UserAccountCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &UserAccountCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for UserAccount. +func (c *UserAccountClient) Update() *UserAccountUpdate { + mutation := newUserAccountMutation(c.config, OpUpdate) + return &UserAccountUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *UserAccountClient) UpdateOne(_m *UserAccount) *UserAccountUpdateOne { + mutation := newUserAccountMutation(c.config, OpUpdateOne, withUserAccount(_m)) + return &UserAccountUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *UserAccountClient) UpdateOneID(id uuid.UUID) *UserAccountUpdateOne { + mutation := newUserAccountMutation(c.config, OpUpdateOne, withUserAccountID(id)) + return &UserAccountUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for UserAccount. +func (c *UserAccountClient) Delete() *UserAccountDelete { + mutation := newUserAccountMutation(c.config, OpDelete) + return &UserAccountDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *UserAccountClient) DeleteOne(_m *UserAccount) *UserAccountDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *UserAccountClient) DeleteOneID(id uuid.UUID) *UserAccountDeleteOne { + builder := c.Delete().Where(useraccount.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &UserAccountDeleteOne{builder} +} + +// Query returns a query builder for UserAccount. +func (c *UserAccountClient) Query() *UserAccountQuery { + return &UserAccountQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeUserAccount}, + inters: c.Interceptors(), + } +} + +// Get returns a UserAccount entity by its id. +func (c *UserAccountClient) Get(ctx context.Context, id uuid.UUID) (*UserAccount, error) { + return c.Query().Where(useraccount.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *UserAccountClient) GetX(ctx context.Context, id uuid.UUID) *UserAccount { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *UserAccountClient) Hooks() []Hook { + return c.hooks.UserAccount +} + +// Interceptors returns the client interceptors. +func (c *UserAccountClient) Interceptors() []Interceptor { + return c.inters.UserAccount +} + +func (c *UserAccountClient) mutate(ctx context.Context, m *UserAccountMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&UserAccountCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&UserAccountUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&UserAccountUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&UserAccountDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown UserAccount mutation op: %q", m.Op()) + } +} + // hooks and interceptors per client, for fast access. type ( hooks struct { - InternalUser []ent.Hook + InternalUser, UserAccount []ent.Hook } inters struct { - InternalUser []ent.Interceptor + InternalUser, UserAccount []ent.Interceptor } ) diff --git a/backend/ent/ent.go b/backend/ent/ent.go index 5e31ef1..e6debe5 100644 --- a/backend/ent/ent.go +++ b/backend/ent/ent.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" ) // ent aliases to avoid import conflicts in user's code. @@ -74,6 +75,7 @@ func checkColumn(t, c string) error { initCheck.Do(func() { columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ internaluser.Table: internaluser.ValidColumn, + useraccount.Table: useraccount.ValidColumn, }) }) return columnCheck(t, c) diff --git a/backend/ent/hook/hook.go b/backend/ent/hook/hook.go index 89bff2d..66cfec8 100644 --- a/backend/ent/hook/hook.go +++ b/backend/ent/hook/hook.go @@ -21,6 +21,18 @@ func (f InternalUserFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.InternalUserMutation", m) } +// The UserAccountFunc type is an adapter to allow the use of ordinary +// function as UserAccount mutator. +type UserAccountFunc func(context.Context, *ent.UserAccountMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f UserAccountFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.UserAccountMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserAccountMutation", m) +} + // Condition is a hook condition function. type Condition func(context.Context, ent.Mutation) bool diff --git a/backend/ent/migrate/schema.go b/backend/ent/migrate/schema.go index 8d6df04..6c8d2c5 100644 --- a/backend/ent/migrate/schema.go +++ b/backend/ent/migrate/schema.go @@ -22,9 +22,24 @@ var ( Columns: InternalUsersColumns, PrimaryKey: []*schema.Column{InternalUsersColumns[0]}, } + // UserAccountsColumns holds the columns for the "user_accounts" table. + UserAccountsColumns = []*schema.Column{ + {Name: "internal_user_id", Type: field.TypeUUID}, + {Name: "provider_enabled", Type: field.TypeBool, Default: false}, + {Name: "onboarding_completed_at", Type: field.TypeTime}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + } + // UserAccountsTable holds the schema information for the "user_accounts" table. + UserAccountsTable = &schema.Table{ + Name: "user_accounts", + Columns: UserAccountsColumns, + PrimaryKey: []*schema.Column{UserAccountsColumns[0]}, + } // Tables holds all the tables in the schema. Tables = []*schema.Table{ InternalUsersTable, + UserAccountsTable, } ) @@ -32,4 +47,7 @@ func init() { InternalUsersTable.Annotation = &entsql.Annotation{ Table: "internal_users", } + UserAccountsTable.Annotation = &entsql.Annotation{ + Table: "user_accounts", + } } diff --git a/backend/ent/mutation.go b/backend/ent/mutation.go index 5954739..5a5bace 100644 --- a/backend/ent/mutation.go +++ b/backend/ent/mutation.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/dialect/sql" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" "github.com/google/uuid" ) @@ -26,6 +27,7 @@ const ( // Node types. TypeInternalUser = "InternalUser" + TypeUserAccount = "UserAccount" ) // InternalUserMutation represents an operation that mutates the InternalUser nodes in the graph. @@ -467,3 +469,497 @@ func (m *InternalUserMutation) ClearEdge(name string) error { func (m *InternalUserMutation) ResetEdge(name string) error { return fmt.Errorf("unknown InternalUser edge %s", name) } + +// UserAccountMutation represents an operation that mutates the UserAccount nodes in the graph. +type UserAccountMutation struct { + config + op Op + typ string + id *uuid.UUID + provider_enabled *bool + onboarding_completed_at *time.Time + created_at *time.Time + updated_at *time.Time + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*UserAccount, error) + predicates []predicate.UserAccount +} + +var _ ent.Mutation = (*UserAccountMutation)(nil) + +// useraccountOption allows management of the mutation configuration using functional options. +type useraccountOption func(*UserAccountMutation) + +// newUserAccountMutation creates new mutation for the UserAccount entity. +func newUserAccountMutation(c config, op Op, opts ...useraccountOption) *UserAccountMutation { + m := &UserAccountMutation{ + config: c, + op: op, + typ: TypeUserAccount, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withUserAccountID sets the ID field of the mutation. +func withUserAccountID(id uuid.UUID) useraccountOption { + return func(m *UserAccountMutation) { + var ( + err error + once sync.Once + value *UserAccount + ) + m.oldValue = func(ctx context.Context) (*UserAccount, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().UserAccount.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withUserAccount sets the old UserAccount of the mutation. +func withUserAccount(node *UserAccount) useraccountOption { + return func(m *UserAccountMutation) { + m.oldValue = func(context.Context) (*UserAccount, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m UserAccountMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m UserAccountMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of UserAccount entities. +func (m *UserAccountMutation) SetID(id uuid.UUID) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *UserAccountMutation) ID() (id uuid.UUID, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *UserAccountMutation) IDs(ctx context.Context) ([]uuid.UUID, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []uuid.UUID{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().UserAccount.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetProviderEnabled sets the "provider_enabled" field. +func (m *UserAccountMutation) SetProviderEnabled(b bool) { + m.provider_enabled = &b +} + +// ProviderEnabled returns the value of the "provider_enabled" field in the mutation. +func (m *UserAccountMutation) ProviderEnabled() (r bool, exists bool) { + v := m.provider_enabled + if v == nil { + return + } + return *v, true +} + +// OldProviderEnabled returns the old "provider_enabled" field's value of the UserAccount entity. +// If the UserAccount object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserAccountMutation) OldProviderEnabled(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldProviderEnabled is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldProviderEnabled requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldProviderEnabled: %w", err) + } + return oldValue.ProviderEnabled, nil +} + +// ResetProviderEnabled resets all changes to the "provider_enabled" field. +func (m *UserAccountMutation) ResetProviderEnabled() { + m.provider_enabled = nil +} + +// SetOnboardingCompletedAt sets the "onboarding_completed_at" field. +func (m *UserAccountMutation) SetOnboardingCompletedAt(t time.Time) { + m.onboarding_completed_at = &t +} + +// OnboardingCompletedAt returns the value of the "onboarding_completed_at" field in the mutation. +func (m *UserAccountMutation) OnboardingCompletedAt() (r time.Time, exists bool) { + v := m.onboarding_completed_at + if v == nil { + return + } + return *v, true +} + +// OldOnboardingCompletedAt returns the old "onboarding_completed_at" field's value of the UserAccount entity. +// If the UserAccount object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserAccountMutation) OldOnboardingCompletedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldOnboardingCompletedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldOnboardingCompletedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldOnboardingCompletedAt: %w", err) + } + return oldValue.OnboardingCompletedAt, nil +} + +// ResetOnboardingCompletedAt resets all changes to the "onboarding_completed_at" field. +func (m *UserAccountMutation) ResetOnboardingCompletedAt() { + m.onboarding_completed_at = nil +} + +// SetCreatedAt sets the "created_at" field. +func (m *UserAccountMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *UserAccountMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the UserAccount entity. +// If the UserAccount object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserAccountMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *UserAccountMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *UserAccountMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *UserAccountMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the UserAccount entity. +// If the UserAccount object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserAccountMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *UserAccountMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// Where appends a list predicates to the UserAccountMutation builder. +func (m *UserAccountMutation) Where(ps ...predicate.UserAccount) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the UserAccountMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *UserAccountMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.UserAccount, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *UserAccountMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *UserAccountMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (UserAccount). +func (m *UserAccountMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *UserAccountMutation) Fields() []string { + fields := make([]string, 0, 4) + if m.provider_enabled != nil { + fields = append(fields, useraccount.FieldProviderEnabled) + } + if m.onboarding_completed_at != nil { + fields = append(fields, useraccount.FieldOnboardingCompletedAt) + } + if m.created_at != nil { + fields = append(fields, useraccount.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, useraccount.FieldUpdatedAt) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *UserAccountMutation) Field(name string) (ent.Value, bool) { + switch name { + case useraccount.FieldProviderEnabled: + return m.ProviderEnabled() + case useraccount.FieldOnboardingCompletedAt: + return m.OnboardingCompletedAt() + case useraccount.FieldCreatedAt: + return m.CreatedAt() + case useraccount.FieldUpdatedAt: + return m.UpdatedAt() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *UserAccountMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case useraccount.FieldProviderEnabled: + return m.OldProviderEnabled(ctx) + case useraccount.FieldOnboardingCompletedAt: + return m.OldOnboardingCompletedAt(ctx) + case useraccount.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case useraccount.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + } + return nil, fmt.Errorf("unknown UserAccount field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserAccountMutation) SetField(name string, value ent.Value) error { + switch name { + case useraccount.FieldProviderEnabled: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetProviderEnabled(v) + return nil + case useraccount.FieldOnboardingCompletedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetOnboardingCompletedAt(v) + return nil + case useraccount.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case useraccount.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown UserAccount field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *UserAccountMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *UserAccountMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserAccountMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown UserAccount numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *UserAccountMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *UserAccountMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *UserAccountMutation) ClearField(name string) error { + return fmt.Errorf("unknown UserAccount nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *UserAccountMutation) ResetField(name string) error { + switch name { + case useraccount.FieldProviderEnabled: + m.ResetProviderEnabled() + return nil + case useraccount.FieldOnboardingCompletedAt: + m.ResetOnboardingCompletedAt() + return nil + case useraccount.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case useraccount.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + } + return fmt.Errorf("unknown UserAccount field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *UserAccountMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *UserAccountMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *UserAccountMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *UserAccountMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *UserAccountMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *UserAccountMutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *UserAccountMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown UserAccount unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *UserAccountMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown UserAccount edge %s", name) +} diff --git a/backend/ent/predicate/predicate.go b/backend/ent/predicate/predicate.go index 7228e2e..8315047 100644 --- a/backend/ent/predicate/predicate.go +++ b/backend/ent/predicate/predicate.go @@ -8,3 +8,6 @@ import ( // InternalUser is the predicate function for internaluser builders. type InternalUser func(*sql.Selector) + +// UserAccount is the predicate function for useraccount builders. +type UserAccount func(*sql.Selector) diff --git a/backend/ent/runtime.go b/backend/ent/runtime.go index a3c9013..af94806 100644 --- a/backend/ent/runtime.go +++ b/backend/ent/runtime.go @@ -7,6 +7,7 @@ import ( "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/schema" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" "github.com/google/uuid" ) @@ -48,4 +49,24 @@ func init() { internaluserDescID := internaluserFields[0].Descriptor() // internaluser.DefaultID holds the default value on creation for the id field. internaluser.DefaultID = internaluserDescID.Default.(func() uuid.UUID) + useraccountFields := schema.UserAccount{}.Fields() + _ = useraccountFields + // useraccountDescProviderEnabled is the schema descriptor for provider_enabled field. + useraccountDescProviderEnabled := useraccountFields[1].Descriptor() + // useraccount.DefaultProviderEnabled holds the default value on creation for the provider_enabled field. + useraccount.DefaultProviderEnabled = useraccountDescProviderEnabled.Default.(bool) + // useraccountDescOnboardingCompletedAt is the schema descriptor for onboarding_completed_at field. + useraccountDescOnboardingCompletedAt := useraccountFields[2].Descriptor() + // useraccount.DefaultOnboardingCompletedAt holds the default value on creation for the onboarding_completed_at field. + useraccount.DefaultOnboardingCompletedAt = useraccountDescOnboardingCompletedAt.Default.(func() time.Time) + // useraccountDescCreatedAt is the schema descriptor for created_at field. + useraccountDescCreatedAt := useraccountFields[3].Descriptor() + // useraccount.DefaultCreatedAt holds the default value on creation for the created_at field. + useraccount.DefaultCreatedAt = useraccountDescCreatedAt.Default.(func() time.Time) + // useraccountDescUpdatedAt is the schema descriptor for updated_at field. + useraccountDescUpdatedAt := useraccountFields[4].Descriptor() + // useraccount.DefaultUpdatedAt holds the default value on creation for the updated_at field. + useraccount.DefaultUpdatedAt = useraccountDescUpdatedAt.Default.(func() time.Time) + // useraccount.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + useraccount.UpdateDefaultUpdatedAt = useraccountDescUpdatedAt.UpdateDefault.(func() time.Time) } diff --git a/backend/ent/schema/useraccount.go b/backend/ent/schema/useraccount.go new file mode 100644 index 0000000..6cbf4ac --- /dev/null +++ b/backend/ent/schema/useraccount.go @@ -0,0 +1,29 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect/entsql" + entschema "entgo.io/ent/schema" + "entgo.io/ent/schema/field" + "github.com/google/uuid" +) + +type UserAccount struct { + ent.Schema +} + +func (UserAccount) Fields() []ent.Field { + return []ent.Field{ + field.UUID("id", uuid.UUID{}).StorageKey("internal_user_id").Immutable(), + field.Bool("provider_enabled").Default(false), + field.Time("onboarding_completed_at").Default(utcNow).Immutable(), + field.Time("created_at").Default(utcNow).Immutable(), + field.Time("updated_at").Default(utcNow).UpdateDefault(utcNow), + } +} + +func (UserAccount) Annotations() []entschema.Annotation { + return []entschema.Annotation{ + entsql.Annotation{Table: "user_accounts"}, + } +} diff --git a/backend/ent/schema/useraccount_test.go b/backend/ent/schema/useraccount_test.go new file mode 100644 index 0000000..144a493 --- /dev/null +++ b/backend/ent/schema/useraccount_test.go @@ -0,0 +1,48 @@ +package schema_test + +import ( + "os" + "strings" + "testing" +) + +func TestUserAccountSchemaContract(t *testing.T) { + t.Parallel() + + contents, err := os.ReadFile("useraccount.go") + if err != nil { + t.Fatalf("read UserAccount schema: %v", err) + } + schema := string(contents) + + for _, requirement := range []string{ + "type UserAccount struct", + "field.UUID(\"id\"", + "StorageKey(\"internal_user_id\")", + "Immutable()", + "field.Bool(\"provider_enabled\")", + "Default(false)", + "field.Time(\"onboarding_completed_at\")", + "field.Time(\"created_at\")", + "field.Time(\"updated_at\")", + "Table: \"user_accounts\"", + } { + if !strings.Contains(schema, requirement) { + t.Errorf("schema does not contain %q", requirement) + } + } + + for _, prohibited := range []string{ + "clerk_subject", + "email", + "phone", + "display_name", + "profile", + "role", + "contact", + } { + if strings.Contains(schema, prohibited) { + t.Errorf("schema must not include %q", prohibited) + } + } +} diff --git a/backend/ent/tx.go b/backend/ent/tx.go index f9e777a..155e778 100644 --- a/backend/ent/tx.go +++ b/backend/ent/tx.go @@ -14,6 +14,8 @@ type Tx struct { config // InternalUser is the client for interacting with the InternalUser builders. InternalUser *InternalUserClient + // UserAccount is the client for interacting with the UserAccount builders. + UserAccount *UserAccountClient // lazily loaded. client *Client @@ -146,6 +148,7 @@ func (tx *Tx) Client() *Client { func (tx *Tx) init() { tx.InternalUser = NewInternalUserClient(tx.config) + tx.UserAccount = NewUserAccountClient(tx.config) } // txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. diff --git a/backend/ent/useraccount.go b/backend/ent/useraccount.go new file mode 100644 index 0000000..d219893 --- /dev/null +++ b/backend/ent/useraccount.go @@ -0,0 +1,140 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" + "github.com/google/uuid" +) + +// UserAccount is the model entity for the UserAccount schema. +type UserAccount struct { + config `json:"-"` + // ID of the ent. + ID uuid.UUID `json:"id,omitempty"` + // ProviderEnabled holds the value of the "provider_enabled" field. + ProviderEnabled bool `json:"provider_enabled,omitempty"` + // OnboardingCompletedAt holds the value of the "onboarding_completed_at" field. + OnboardingCompletedAt time.Time `json:"onboarding_completed_at,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*UserAccount) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case useraccount.FieldProviderEnabled: + values[i] = new(sql.NullBool) + case useraccount.FieldOnboardingCompletedAt, useraccount.FieldCreatedAt, useraccount.FieldUpdatedAt: + values[i] = new(sql.NullTime) + case useraccount.FieldID: + values[i] = new(uuid.UUID) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the UserAccount fields. +func (_m *UserAccount) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case useraccount.FieldID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value != nil { + _m.ID = *value + } + case useraccount.FieldProviderEnabled: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field provider_enabled", values[i]) + } else if value.Valid { + _m.ProviderEnabled = value.Bool + } + case useraccount.FieldOnboardingCompletedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field onboarding_completed_at", values[i]) + } else if value.Valid { + _m.OnboardingCompletedAt = value.Time + } + case useraccount.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + _m.CreatedAt = value.Time + } + case useraccount.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + _m.UpdatedAt = value.Time + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the UserAccount. +// This includes values selected through modifiers, order, etc. +func (_m *UserAccount) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// Update returns a builder for updating this UserAccount. +// Note that you need to call UserAccount.Unwrap() before calling this method if this UserAccount +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *UserAccount) Update() *UserAccountUpdateOne { + return NewUserAccountClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the UserAccount entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *UserAccount) Unwrap() *UserAccount { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: UserAccount is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *UserAccount) String() string { + var builder strings.Builder + builder.WriteString("UserAccount(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("provider_enabled=") + builder.WriteString(fmt.Sprintf("%v", _m.ProviderEnabled)) + builder.WriteString(", ") + builder.WriteString("onboarding_completed_at=") + builder.WriteString(_m.OnboardingCompletedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("created_at=") + builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(_m.UpdatedAt.Format(time.ANSIC)) + builder.WriteByte(')') + return builder.String() +} + +// UserAccounts is a parsable slice of UserAccount. +type UserAccounts []*UserAccount diff --git a/backend/ent/useraccount/useraccount.go b/backend/ent/useraccount/useraccount.go new file mode 100644 index 0000000..06da900 --- /dev/null +++ b/backend/ent/useraccount/useraccount.go @@ -0,0 +1,86 @@ +// Code generated by ent, DO NOT EDIT. + +package useraccount + +import ( + "time" + + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the useraccount type in the database. + Label = "user_account" + // FieldID holds the string denoting the id field in the database. + FieldID = "internal_user_id" + // FieldProviderEnabled holds the string denoting the provider_enabled field in the database. + FieldProviderEnabled = "provider_enabled" + // FieldOnboardingCompletedAt holds the string denoting the onboarding_completed_at field in the database. + FieldOnboardingCompletedAt = "onboarding_completed_at" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // Table holds the table name of the useraccount in the database. + Table = "user_accounts" +) + +// Columns holds all SQL columns for useraccount fields. +var Columns = []string{ + FieldID, + FieldProviderEnabled, + FieldOnboardingCompletedAt, + FieldCreatedAt, + FieldUpdatedAt, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultProviderEnabled holds the default value on creation for the "provider_enabled" field. + DefaultProviderEnabled bool + // DefaultOnboardingCompletedAt holds the default value on creation for the "onboarding_completed_at" field. + DefaultOnboardingCompletedAt func() time.Time + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time +) + +// OrderOption defines the ordering options for the UserAccount queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByProviderEnabled orders the results by the provider_enabled field. +func ByProviderEnabled(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldProviderEnabled, opts...).ToFunc() +} + +// ByOnboardingCompletedAt orders the results by the onboarding_completed_at field. +func ByOnboardingCompletedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldOnboardingCompletedAt, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} diff --git a/backend/ent/useraccount/where.go b/backend/ent/useraccount/where.go new file mode 100644 index 0000000..7f48636 --- /dev/null +++ b/backend/ent/useraccount/where.go @@ -0,0 +1,221 @@ +// Code generated by ent, DO NOT EDIT. + +package useraccount + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +// ID filters vertices based on their ID field. +func ID(id uuid.UUID) predicate.UserAccount { + return predicate.UserAccount(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id uuid.UUID) predicate.UserAccount { + return predicate.UserAccount(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id uuid.UUID) predicate.UserAccount { + return predicate.UserAccount(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...uuid.UUID) predicate.UserAccount { + return predicate.UserAccount(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...uuid.UUID) predicate.UserAccount { + return predicate.UserAccount(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id uuid.UUID) predicate.UserAccount { + return predicate.UserAccount(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id uuid.UUID) predicate.UserAccount { + return predicate.UserAccount(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id uuid.UUID) predicate.UserAccount { + return predicate.UserAccount(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id uuid.UUID) predicate.UserAccount { + return predicate.UserAccount(sql.FieldLTE(FieldID, id)) +} + +// ProviderEnabled applies equality check predicate on the "provider_enabled" field. It's identical to ProviderEnabledEQ. +func ProviderEnabled(v bool) predicate.UserAccount { + return predicate.UserAccount(sql.FieldEQ(FieldProviderEnabled, v)) +} + +// OnboardingCompletedAt applies equality check predicate on the "onboarding_completed_at" field. It's identical to OnboardingCompletedAtEQ. +func OnboardingCompletedAt(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldEQ(FieldOnboardingCompletedAt, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// ProviderEnabledEQ applies the EQ predicate on the "provider_enabled" field. +func ProviderEnabledEQ(v bool) predicate.UserAccount { + return predicate.UserAccount(sql.FieldEQ(FieldProviderEnabled, v)) +} + +// ProviderEnabledNEQ applies the NEQ predicate on the "provider_enabled" field. +func ProviderEnabledNEQ(v bool) predicate.UserAccount { + return predicate.UserAccount(sql.FieldNEQ(FieldProviderEnabled, v)) +} + +// OnboardingCompletedAtEQ applies the EQ predicate on the "onboarding_completed_at" field. +func OnboardingCompletedAtEQ(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldEQ(FieldOnboardingCompletedAt, v)) +} + +// OnboardingCompletedAtNEQ applies the NEQ predicate on the "onboarding_completed_at" field. +func OnboardingCompletedAtNEQ(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldNEQ(FieldOnboardingCompletedAt, v)) +} + +// OnboardingCompletedAtIn applies the In predicate on the "onboarding_completed_at" field. +func OnboardingCompletedAtIn(vs ...time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldIn(FieldOnboardingCompletedAt, vs...)) +} + +// OnboardingCompletedAtNotIn applies the NotIn predicate on the "onboarding_completed_at" field. +func OnboardingCompletedAtNotIn(vs ...time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldNotIn(FieldOnboardingCompletedAt, vs...)) +} + +// OnboardingCompletedAtGT applies the GT predicate on the "onboarding_completed_at" field. +func OnboardingCompletedAtGT(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldGT(FieldOnboardingCompletedAt, v)) +} + +// OnboardingCompletedAtGTE applies the GTE predicate on the "onboarding_completed_at" field. +func OnboardingCompletedAtGTE(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldGTE(FieldOnboardingCompletedAt, v)) +} + +// OnboardingCompletedAtLT applies the LT predicate on the "onboarding_completed_at" field. +func OnboardingCompletedAtLT(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldLT(FieldOnboardingCompletedAt, v)) +} + +// OnboardingCompletedAtLTE applies the LTE predicate on the "onboarding_completed_at" field. +func OnboardingCompletedAtLTE(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldLTE(FieldOnboardingCompletedAt, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.UserAccount { + return predicate.UserAccount(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.UserAccount) predicate.UserAccount { + return predicate.UserAccount(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.UserAccount) predicate.UserAccount { + return predicate.UserAccount(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.UserAccount) predicate.UserAccount { + return predicate.UserAccount(sql.NotPredicates(p)) +} diff --git a/backend/ent/useraccount_create.go b/backend/ent/useraccount_create.go new file mode 100644 index 0000000..5715b0f --- /dev/null +++ b/backend/ent/useraccount_create.go @@ -0,0 +1,289 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" + "github.com/google/uuid" +) + +// UserAccountCreate is the builder for creating a UserAccount entity. +type UserAccountCreate struct { + config + mutation *UserAccountMutation + hooks []Hook +} + +// SetProviderEnabled sets the "provider_enabled" field. +func (_c *UserAccountCreate) SetProviderEnabled(v bool) *UserAccountCreate { + _c.mutation.SetProviderEnabled(v) + return _c +} + +// SetNillableProviderEnabled sets the "provider_enabled" field if the given value is not nil. +func (_c *UserAccountCreate) SetNillableProviderEnabled(v *bool) *UserAccountCreate { + if v != nil { + _c.SetProviderEnabled(*v) + } + return _c +} + +// SetOnboardingCompletedAt sets the "onboarding_completed_at" field. +func (_c *UserAccountCreate) SetOnboardingCompletedAt(v time.Time) *UserAccountCreate { + _c.mutation.SetOnboardingCompletedAt(v) + return _c +} + +// SetNillableOnboardingCompletedAt sets the "onboarding_completed_at" field if the given value is not nil. +func (_c *UserAccountCreate) SetNillableOnboardingCompletedAt(v *time.Time) *UserAccountCreate { + if v != nil { + _c.SetOnboardingCompletedAt(*v) + } + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *UserAccountCreate) SetCreatedAt(v time.Time) *UserAccountCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *UserAccountCreate) SetNillableCreatedAt(v *time.Time) *UserAccountCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *UserAccountCreate) SetUpdatedAt(v time.Time) *UserAccountCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (_c *UserAccountCreate) SetNillableUpdatedAt(v *time.Time) *UserAccountCreate { + if v != nil { + _c.SetUpdatedAt(*v) + } + return _c +} + +// SetID sets the "id" field. +func (_c *UserAccountCreate) SetID(v uuid.UUID) *UserAccountCreate { + _c.mutation.SetID(v) + return _c +} + +// Mutation returns the UserAccountMutation object of the builder. +func (_c *UserAccountCreate) Mutation() *UserAccountMutation { + return _c.mutation +} + +// Save creates the UserAccount in the database. +func (_c *UserAccountCreate) Save(ctx context.Context) (*UserAccount, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *UserAccountCreate) SaveX(ctx context.Context) *UserAccount { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *UserAccountCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *UserAccountCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *UserAccountCreate) defaults() { + if _, ok := _c.mutation.ProviderEnabled(); !ok { + v := useraccount.DefaultProviderEnabled + _c.mutation.SetProviderEnabled(v) + } + if _, ok := _c.mutation.OnboardingCompletedAt(); !ok { + v := useraccount.DefaultOnboardingCompletedAt() + _c.mutation.SetOnboardingCompletedAt(v) + } + if _, ok := _c.mutation.CreatedAt(); !ok { + v := useraccount.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + v := useraccount.DefaultUpdatedAt() + _c.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *UserAccountCreate) check() error { + if _, ok := _c.mutation.ProviderEnabled(); !ok { + return &ValidationError{Name: "provider_enabled", err: errors.New(`ent: missing required field "UserAccount.provider_enabled"`)} + } + if _, ok := _c.mutation.OnboardingCompletedAt(); !ok { + return &ValidationError{Name: "onboarding_completed_at", err: errors.New(`ent: missing required field "UserAccount.onboarding_completed_at"`)} + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "UserAccount.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "UserAccount.updated_at"`)} + } + return nil +} + +func (_c *UserAccountCreate) sqlSave(ctx context.Context) (*UserAccount, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(*uuid.UUID); ok { + _node.ID = *id + } else if err := _node.ID.Scan(_spec.ID.Value); err != nil { + return nil, err + } + } + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *UserAccountCreate) createSpec() (*UserAccount, *sqlgraph.CreateSpec) { + var ( + _node = &UserAccount{config: _c.config} + _spec = sqlgraph.NewCreateSpec(useraccount.Table, sqlgraph.NewFieldSpec(useraccount.FieldID, field.TypeUUID)) + ) + if id, ok := _c.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = &id + } + if value, ok := _c.mutation.ProviderEnabled(); ok { + _spec.SetField(useraccount.FieldProviderEnabled, field.TypeBool, value) + _node.ProviderEnabled = value + } + if value, ok := _c.mutation.OnboardingCompletedAt(); ok { + _spec.SetField(useraccount.FieldOnboardingCompletedAt, field.TypeTime, value) + _node.OnboardingCompletedAt = value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(useraccount.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(useraccount.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + return _node, _spec +} + +// UserAccountCreateBulk is the builder for creating many UserAccount entities in bulk. +type UserAccountCreateBulk struct { + config + err error + builders []*UserAccountCreate +} + +// Save creates the UserAccount entities in the database. +func (_c *UserAccountCreateBulk) Save(ctx context.Context) ([]*UserAccount, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*UserAccount, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*UserAccountMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *UserAccountCreateBulk) SaveX(ctx context.Context) []*UserAccount { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *UserAccountCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *UserAccountCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/useraccount_delete.go b/backend/ent/useraccount_delete.go new file mode 100644 index 0000000..0f62b47 --- /dev/null +++ b/backend/ent/useraccount_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" +) + +// UserAccountDelete is the builder for deleting a UserAccount entity. +type UserAccountDelete struct { + config + hooks []Hook + mutation *UserAccountMutation +} + +// Where appends a list predicates to the UserAccountDelete builder. +func (_d *UserAccountDelete) Where(ps ...predicate.UserAccount) *UserAccountDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *UserAccountDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *UserAccountDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *UserAccountDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(useraccount.Table, sqlgraph.NewFieldSpec(useraccount.FieldID, field.TypeUUID)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// UserAccountDeleteOne is the builder for deleting a single UserAccount entity. +type UserAccountDeleteOne struct { + _d *UserAccountDelete +} + +// Where appends a list predicates to the UserAccountDelete builder. +func (_d *UserAccountDeleteOne) Where(ps ...predicate.UserAccount) *UserAccountDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *UserAccountDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{useraccount.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *UserAccountDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/useraccount_query.go b/backend/ent/useraccount_query.go new file mode 100644 index 0000000..2858282 --- /dev/null +++ b/backend/ent/useraccount_query.go @@ -0,0 +1,528 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" + "github.com/google/uuid" +) + +// UserAccountQuery is the builder for querying UserAccount entities. +type UserAccountQuery struct { + config + ctx *QueryContext + order []useraccount.OrderOption + inters []Interceptor + predicates []predicate.UserAccount + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the UserAccountQuery builder. +func (_q *UserAccountQuery) Where(ps ...predicate.UserAccount) *UserAccountQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *UserAccountQuery) Limit(limit int) *UserAccountQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *UserAccountQuery) Offset(offset int) *UserAccountQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *UserAccountQuery) Unique(unique bool) *UserAccountQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *UserAccountQuery) Order(o ...useraccount.OrderOption) *UserAccountQuery { + _q.order = append(_q.order, o...) + return _q +} + +// First returns the first UserAccount entity from the query. +// Returns a *NotFoundError when no UserAccount was found. +func (_q *UserAccountQuery) First(ctx context.Context) (*UserAccount, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{useraccount.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *UserAccountQuery) FirstX(ctx context.Context) *UserAccount { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first UserAccount ID from the query. +// Returns a *NotFoundError when no UserAccount ID was found. +func (_q *UserAccountQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{useraccount.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *UserAccountQuery) FirstIDX(ctx context.Context) uuid.UUID { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single UserAccount entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one UserAccount entity is found. +// Returns a *NotFoundError when no UserAccount entities are found. +func (_q *UserAccountQuery) Only(ctx context.Context) (*UserAccount, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{useraccount.Label} + default: + return nil, &NotSingularError{useraccount.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *UserAccountQuery) OnlyX(ctx context.Context) *UserAccount { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only UserAccount ID in the query. +// Returns a *NotSingularError when more than one UserAccount ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *UserAccountQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{useraccount.Label} + default: + err = &NotSingularError{useraccount.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *UserAccountQuery) OnlyIDX(ctx context.Context) uuid.UUID { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of UserAccounts. +func (_q *UserAccountQuery) All(ctx context.Context) ([]*UserAccount, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*UserAccount, *UserAccountQuery]() + return withInterceptors[[]*UserAccount](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *UserAccountQuery) AllX(ctx context.Context) []*UserAccount { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of UserAccount IDs. +func (_q *UserAccountQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(useraccount.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *UserAccountQuery) IDsX(ctx context.Context) []uuid.UUID { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *UserAccountQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*UserAccountQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *UserAccountQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *UserAccountQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *UserAccountQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the UserAccountQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *UserAccountQuery) Clone() *UserAccountQuery { + if _q == nil { + return nil + } + return &UserAccountQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]useraccount.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.UserAccount{}, _q.predicates...), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// ProviderEnabled bool `json:"provider_enabled,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.UserAccount.Query(). +// GroupBy(useraccount.FieldProviderEnabled). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *UserAccountQuery) GroupBy(field string, fields ...string) *UserAccountGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &UserAccountGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = useraccount.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// ProviderEnabled bool `json:"provider_enabled,omitempty"` +// } +// +// client.UserAccount.Query(). +// Select(useraccount.FieldProviderEnabled). +// Scan(ctx, &v) +func (_q *UserAccountQuery) Select(fields ...string) *UserAccountSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &UserAccountSelect{UserAccountQuery: _q} + sbuild.label = useraccount.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a UserAccountSelect configured with the given aggregations. +func (_q *UserAccountQuery) Aggregate(fns ...AggregateFunc) *UserAccountSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *UserAccountQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !useraccount.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *UserAccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*UserAccount, error) { + var ( + nodes = []*UserAccount{} + _spec = _q.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*UserAccount).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &UserAccount{config: _q.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (_q *UserAccountQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *UserAccountQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(useraccount.Table, useraccount.Columns, sqlgraph.NewFieldSpec(useraccount.FieldID, field.TypeUUID)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, useraccount.FieldID) + for i := range fields { + if fields[i] != useraccount.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *UserAccountQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(useraccount.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = useraccount.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// UserAccountGroupBy is the group-by builder for UserAccount entities. +type UserAccountGroupBy struct { + selector + build *UserAccountQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *UserAccountGroupBy) Aggregate(fns ...AggregateFunc) *UserAccountGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *UserAccountGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserAccountQuery, *UserAccountGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *UserAccountGroupBy) sqlScan(ctx context.Context, root *UserAccountQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// UserAccountSelect is the builder for selecting fields of UserAccount entities. +type UserAccountSelect struct { + *UserAccountQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *UserAccountSelect) Aggregate(fns ...AggregateFunc) *UserAccountSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *UserAccountSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserAccountQuery, *UserAccountSelect](ctx, _s.UserAccountQuery, _s, _s.inters, v) +} + +func (_s *UserAccountSelect) sqlScan(ctx context.Context, root *UserAccountQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/useraccount_update.go b/backend/ent/useraccount_update.go new file mode 100644 index 0000000..9bba537 --- /dev/null +++ b/backend/ent/useraccount_update.go @@ -0,0 +1,246 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" +) + +// UserAccountUpdate is the builder for updating UserAccount entities. +type UserAccountUpdate struct { + config + hooks []Hook + mutation *UserAccountMutation +} + +// Where appends a list predicates to the UserAccountUpdate builder. +func (_u *UserAccountUpdate) Where(ps ...predicate.UserAccount) *UserAccountUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetProviderEnabled sets the "provider_enabled" field. +func (_u *UserAccountUpdate) SetProviderEnabled(v bool) *UserAccountUpdate { + _u.mutation.SetProviderEnabled(v) + return _u +} + +// SetNillableProviderEnabled sets the "provider_enabled" field if the given value is not nil. +func (_u *UserAccountUpdate) SetNillableProviderEnabled(v *bool) *UserAccountUpdate { + if v != nil { + _u.SetProviderEnabled(*v) + } + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *UserAccountUpdate) SetUpdatedAt(v time.Time) *UserAccountUpdate { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// Mutation returns the UserAccountMutation object of the builder. +func (_u *UserAccountUpdate) Mutation() *UserAccountMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *UserAccountUpdate) Save(ctx context.Context) (int, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *UserAccountUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *UserAccountUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *UserAccountUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *UserAccountUpdate) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := useraccount.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +func (_u *UserAccountUpdate) sqlSave(ctx context.Context) (_node int, err error) { + _spec := sqlgraph.NewUpdateSpec(useraccount.Table, useraccount.Columns, sqlgraph.NewFieldSpec(useraccount.FieldID, field.TypeUUID)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.ProviderEnabled(); ok { + _spec.SetField(useraccount.FieldProviderEnabled, field.TypeBool, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(useraccount.FieldUpdatedAt, field.TypeTime, value) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{useraccount.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// UserAccountUpdateOne is the builder for updating a single UserAccount entity. +type UserAccountUpdateOne struct { + config + fields []string + hooks []Hook + mutation *UserAccountMutation +} + +// SetProviderEnabled sets the "provider_enabled" field. +func (_u *UserAccountUpdateOne) SetProviderEnabled(v bool) *UserAccountUpdateOne { + _u.mutation.SetProviderEnabled(v) + return _u +} + +// SetNillableProviderEnabled sets the "provider_enabled" field if the given value is not nil. +func (_u *UserAccountUpdateOne) SetNillableProviderEnabled(v *bool) *UserAccountUpdateOne { + if v != nil { + _u.SetProviderEnabled(*v) + } + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *UserAccountUpdateOne) SetUpdatedAt(v time.Time) *UserAccountUpdateOne { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// Mutation returns the UserAccountMutation object of the builder. +func (_u *UserAccountUpdateOne) Mutation() *UserAccountMutation { + return _u.mutation +} + +// Where appends a list predicates to the UserAccountUpdate builder. +func (_u *UserAccountUpdateOne) Where(ps ...predicate.UserAccount) *UserAccountUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *UserAccountUpdateOne) Select(field string, fields ...string) *UserAccountUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated UserAccount entity. +func (_u *UserAccountUpdateOne) Save(ctx context.Context) (*UserAccount, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *UserAccountUpdateOne) SaveX(ctx context.Context) *UserAccount { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *UserAccountUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *UserAccountUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *UserAccountUpdateOne) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := useraccount.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +func (_u *UserAccountUpdateOne) sqlSave(ctx context.Context) (_node *UserAccount, err error) { + _spec := sqlgraph.NewUpdateSpec(useraccount.Table, useraccount.Columns, sqlgraph.NewFieldSpec(useraccount.FieldID, field.TypeUUID)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "UserAccount.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, useraccount.FieldID) + for _, f := range fields { + if !useraccount.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != useraccount.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.ProviderEnabled(); ok { + _spec.SetField(useraccount.FieldProviderEnabled, field.TypeBool, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(useraccount.FieldUpdatedAt, field.TypeTime, value) + } + _node = &UserAccount{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{useraccount.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/go.sum b/backend/go.sum index acb5c3a..c7fe75e 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -12,9 +12,17 @@ github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQ github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/clerk/clerk-sdk-go/v2 v2.7.0 h1:Bc/hbqpXdPsaNpp9ppOzL3I0R5+8jVeZ5FvgB+bPv0o= github.com/clerk/clerk-sdk-go/v2 v2.7.0/go.mod h1:ncFmsPwmD5WpGCNW5bJve862j/HQfpkzsshXYV/quJ8= +github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo= +github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= +github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= @@ -28,6 +36,8 @@ 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/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo= github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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= @@ -42,16 +52,34 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 h1:jrYnow5+hy3WRDCBypUFvVKNSPPCdqgSXIE9eJDD8LM= +github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= +github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA= +github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM= 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/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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= @@ -89,6 +117,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -106,6 +136,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= diff --git a/backend/internal/users/migration_contract_test.go b/backend/internal/users/migration_contract_test.go index 09c7bfa..c02f91b 100644 --- a/backend/internal/users/migration_contract_test.go +++ b/backend/internal/users/migration_contract_test.go @@ -46,3 +46,58 @@ func TestCreateInternalUsersMigrationContract(t *testing.T) { } } } + +func TestCreateUserAccountsMigrationContract(t *testing.T) { + t.Parallel() + + migrationDirectory := filepath.Join("..", "..", "..", "supabase", "migrations") + entries, err := os.ReadDir(migrationDirectory) + if err != nil { + t.Fatalf("read migration directory: %v", err) + } + + var migrationPath string + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), "_create_user_accounts.sql") { + migrationPath = filepath.Join(migrationDirectory, entry.Name()) + break + } + } + if migrationPath == "" { + t.Fatal("user-accounts migration was not found") + } + + contents, err := os.ReadFile(migrationPath) + if err != nil { + t.Fatalf("read migration: %v", err) + } + migration := strings.ToLower(string(contents)) + + for _, requirement := range []string{ + "create table public.user_accounts", + "internal_user_id uuid primary key", + "references public.internal_users(id) on delete cascade", + "provider_enabled boolean not null default false", + "onboarding_completed_at timestamptz not null", + "created_at timestamptz not null", + "updated_at timestamptz not null", + } { + if !strings.Contains(migration, requirement) { + t.Errorf("migration does not contain %q", requirement) + } + } + + for _, prohibited := range []string{ + "clerk_subject", + "email", + "phone", + "display_name", + "profile", + "role", + "contact", + } { + if strings.Contains(migration, prohibited) { + t.Errorf("migration must not include %q", prohibited) + } + } +} diff --git a/supabase/migrations/20260823143901_create_user_accounts.sql b/supabase/migrations/20260823143901_create_user_accounts.sql new file mode 100644 index 0000000..30a354b --- /dev/null +++ b/supabase/migrations/20260823143901_create_user_accounts.sql @@ -0,0 +1,7 @@ +create table public.user_accounts ( + internal_user_id uuid primary key references public.internal_users(id) on delete cascade, + provider_enabled boolean not null default false, + onboarding_completed_at timestamptz not null default timezone('utc', now()), + created_at timestamptz not null default timezone('utc', now()), + updated_at timestamptz not null default timezone('utc', now()) +); From ed2962804e4900e72bd5ab7f1df89a29246a445b Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 14:54:16 +0100 Subject: [PATCH 07/64] feat: add account capability service --- backend/internal/accounts/ent_repository.go | 75 ++++++ .../internal/accounts/ent_repository_test.go | 165 ++++++++++++ backend/internal/accounts/model.go | 29 +++ backend/internal/accounts/repository.go | 20 ++ backend/internal/accounts/service.go | 83 +++++++ backend/internal/accounts/service_test.go | 234 ++++++++++++++++++ 6 files changed, 606 insertions(+) create mode 100644 backend/internal/accounts/ent_repository.go create mode 100644 backend/internal/accounts/ent_repository_test.go create mode 100644 backend/internal/accounts/model.go create mode 100644 backend/internal/accounts/repository.go create mode 100644 backend/internal/accounts/service.go create mode 100644 backend/internal/accounts/service_test.go diff --git a/backend/internal/accounts/ent_repository.go b/backend/internal/accounts/ent_repository.go new file mode 100644 index 0000000..c5c332f --- /dev/null +++ b/backend/internal/accounts/ent_repository.go @@ -0,0 +1,75 @@ +package accounts + +import ( + "context" + "errors" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/ent" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" + "github.com/google/uuid" +) + +type entRepository struct { + client *ent.Client +} + +func NewEntRepository(client *ent.Client) Repository { + return entRepository{client: client} +} + +func (r entRepository) FindByInternalUserID(ctx context.Context, internalUserID uuid.UUID) (Record, bool, error) { + if r.client == nil { + return Record{}, false, errors.New("Ent client is nil") + } + + entity, err := r.client.UserAccount.Query().Where(useraccount.IDEQ(internalUserID)).Only(ctx) + if err != nil { + if ent.IsNotFound(err) { + return Record{}, false, nil + } + return Record{}, false, err + } + return recordFromEnt(entity), true, nil +} + +func (r entRepository) Create(ctx context.Context, internalUserID uuid.UUID) (Record, error) { + if r.client == nil { + return Record{}, errors.New("Ent client is nil") + } + + entity, err := r.client.UserAccount.Create().SetID(internalUserID).Save(ctx) + if err != nil { + if ent.IsConstraintError(err) { + return Record{}, ErrAccountConflict + } + return Record{}, err + } + return recordFromEnt(entity), nil +} + +func (r entRepository) SetProviderEnabled(ctx context.Context, internalUserID uuid.UUID, enabled bool) (Record, error) { + if r.client == nil { + return Record{}, errors.New("Ent client is nil") + } + + entity, err := r.client.UserAccount.UpdateOneID(internalUserID).SetProviderEnabled(enabled).Save(ctx) + if err != nil { + return Record{}, err + } + return recordFromEnt(entity), nil +} + +func recordFromEnt(entity *ent.UserAccount) Record { + return Record{ + InternalUserID: entity.ID, + ProviderEnabled: entity.ProviderEnabled, + OnboardingCompletedAt: normalizeDatabaseTime(entity.OnboardingCompletedAt), + CreatedAt: normalizeDatabaseTime(entity.CreatedAt), + UpdatedAt: normalizeDatabaseTime(entity.UpdatedAt), + } +} + +func normalizeDatabaseTime(value time.Time) time.Time { + return value.UTC().Truncate(time.Microsecond) +} diff --git a/backend/internal/accounts/ent_repository_test.go b/backend/internal/accounts/ent_repository_test.go new file mode 100644 index 0000000..a95771c --- /dev/null +++ b/backend/internal/accounts/ent_repository_test.go @@ -0,0 +1,165 @@ +package accounts + +import ( + "context" + "database/sql" + "os" + "sync" + "testing" + + "entgo.io/ent/dialect" + entsql "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" + "github.com/google/uuid" + _ "github.com/jackc/pgx/v5/stdlib" +) + +func TestEntRepositoryCreatesFindsAndUpdatesAccount(t *testing.T) { + client := openAccountIntegrationClient(t) + ctx := context.Background() + identity := users.VerifiedIdentity{Subject: "test_account_" + uuid.NewString()} + internalUser, _, err := users.NewService(users.NewEntRepository(client)).Reconcile(ctx, identity) + if err != nil { + t.Fatalf("reconcile internal user: %v", err) + } + t.Cleanup(func() { + if err := client.InternalUser.DeleteOneID(internalUser.ID).Exec(ctx); err != nil { + t.Errorf("cleanup internal user: %v", err) + } + }) + + repository := NewEntRepository(client) + created, err := repository.Create(ctx, internalUser.ID) + if err != nil { + t.Fatalf("create account: %v", err) + } + if created.InternalUserID != internalUser.ID { + t.Fatalf("internal user ID = %s, want %s", created.InternalUserID, internalUser.ID) + } + if created.ProviderEnabled { + t.Fatal("provider capability must default to false") + } + + found, exists, err := repository.FindByInternalUserID(ctx, internalUser.ID) + if err != nil { + t.Fatalf("find account: %v", err) + } + if !exists || found != created { + t.Fatalf("found = %#v exists=%t, want %#v true", found, exists, created) + } + + enabled, err := repository.SetProviderEnabled(ctx, internalUser.ID, true) + if err != nil { + t.Fatalf("enable provider capability: %v", err) + } + if !enabled.ProviderEnabled { + t.Fatal("provider capability was not enabled") + } + if enabled.OnboardingCompletedAt != created.OnboardingCompletedAt { + t.Fatalf("onboarding timestamp changed: got %s want %s", enabled.OnboardingCompletedAt, created.OnboardingCompletedAt) + } + + disabled, err := repository.SetProviderEnabled(ctx, internalUser.ID, false) + if err != nil { + t.Fatalf("disable provider capability: %v", err) + } + if disabled.ProviderEnabled { + t.Fatal("provider capability was not disabled") + } + if disabled.OnboardingCompletedAt != created.OnboardingCompletedAt { + t.Fatalf("onboarding timestamp changed: got %s want %s", disabled.OnboardingCompletedAt, created.OnboardingCompletedAt) + } +} + +func TestConcurrentFirstAccountReadsProduceOneStableRow(t *testing.T) { + client := openAccountIntegrationClient(t) + ctx := context.Background() + identity := users.VerifiedIdentity{Subject: "test_account_race_" + uuid.NewString()} + identityService := users.NewService(users.NewEntRepository(client)) + accountService := NewService(identityService, NewEntRepository(client)) + + const attempts = 8 + start := make(chan struct{}) + results := make(chan Account, attempts) + errs := make(chan error, attempts) + var workers sync.WaitGroup + for range attempts { + workers.Add(1) + go func() { + defer workers.Done() + <-start + account, err := accountService.Get(ctx, identity) + if err != nil { + errs <- err + return + } + results <- account + }() + } + close(start) + workers.Wait() + close(results) + close(errs) + + for err := range errs { + t.Fatalf("get account: %v", err) + } + + var stable Account + for account := range results { + if stable.OnboardingCompletedAt.IsZero() { + stable = account + continue + } + if account != stable { + t.Fatalf("account = %#v, want stable %#v", account, stable) + } + } + if stable.OnboardingCompletedAt.IsZero() { + t.Fatal("no account result") + } + if !stable.CustomerEnabled || stable.ProviderEnabled { + t.Fatalf("capabilities = customer:%t provider:%t, want true:false", stable.CustomerEnabled, stable.ProviderEnabled) + } + + internalUser, _, err := identityService.Reconcile(ctx, identity) + if err != nil { + t.Fatalf("reload internal user: %v", err) + } + t.Cleanup(func() { + if err := client.InternalUser.DeleteOneID(internalUser.ID).Exec(ctx); err != nil { + t.Errorf("cleanup internal user: %v", err) + } + }) + + count, err := client.UserAccount.Query().Where(useraccount.IDEQ(internalUser.ID)).Count(ctx) + if err != nil { + t.Fatalf("count account rows: %v", err) + } + if count != 1 { + t.Fatalf("account row count = %d, want 1", count) + } +} + +func openAccountIntegrationClient(t *testing.T) *ent.Client { + t.Helper() + + databaseURL := os.Getenv("TEST_DATABASE_URL") + if databaseURL == "" { + t.Skip("TEST_DATABASE_URL is required for PostgreSQL integration tests") + } + + database, err := sql.Open("pgx", databaseURL) + if err != nil { + t.Fatalf("open pgx database: %v", err) + } + client := ent.NewClient(ent.Driver(entsql.OpenDB(dialect.Postgres, database))) + t.Cleanup(func() { + if err := client.Close(); err != nil { + t.Errorf("close Ent client: %v", err) + } + }) + return client +} diff --git a/backend/internal/accounts/model.go b/backend/internal/accounts/model.go new file mode 100644 index 0000000..0aad287 --- /dev/null +++ b/backend/internal/accounts/model.go @@ -0,0 +1,29 @@ +package accounts + +import ( + "time" + + "github.com/google/uuid" +) + +type Account struct { + CustomerEnabled bool + ProviderEnabled bool + OnboardingCompletedAt time.Time +} + +type Record struct { + InternalUserID uuid.UUID + ProviderEnabled bool + OnboardingCompletedAt time.Time + CreatedAt time.Time + UpdatedAt time.Time +} + +func accountFromRecord(record Record) Account { + return Account{ + CustomerEnabled: true, + ProviderEnabled: record.ProviderEnabled, + OnboardingCompletedAt: record.OnboardingCompletedAt, + } +} diff --git a/backend/internal/accounts/repository.go b/backend/internal/accounts/repository.go new file mode 100644 index 0000000..7553ae0 --- /dev/null +++ b/backend/internal/accounts/repository.go @@ -0,0 +1,20 @@ +package accounts + +import ( + "context" + "errors" + + "github.com/google/uuid" +) + +var ( + ErrAccountConflict = errors.New("user account conflict") + ErrUnavailable = errors.New("user account persistence unavailable") + ErrInvalidIdentity = errors.New("invalid verified identity") +) + +type Repository interface { + FindByInternalUserID(context.Context, uuid.UUID) (Record, bool, error) + Create(context.Context, uuid.UUID) (Record, error) + SetProviderEnabled(context.Context, uuid.UUID, bool) (Record, error) +} diff --git a/backend/internal/accounts/service.go b/backend/internal/accounts/service.go new file mode 100644 index 0000000..9bd089a --- /dev/null +++ b/backend/internal/accounts/service.go @@ -0,0 +1,83 @@ +package accounts + +import ( + "context" + "errors" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" +) + +type IdentityReconciler interface { + Reconcile(context.Context, users.VerifiedIdentity) (users.InternalUser, bool, error) +} + +type Service interface { + Get(context.Context, users.VerifiedIdentity) (Account, error) + SetProviderEnabled(context.Context, users.VerifiedIdentity, bool) (Account, error) +} + +type service struct { + identities IdentityReconciler + repository Repository +} + +func NewService(identities IdentityReconciler, repository Repository) Service { + return service{identities: identities, repository: repository} +} + +func (s service) Get(ctx context.Context, identity users.VerifiedIdentity) (Account, error) { + record, err := s.reconcileAccount(ctx, identity) + if err != nil { + return Account{}, err + } + return accountFromRecord(record), nil +} + +func (s service) SetProviderEnabled(ctx context.Context, identity users.VerifiedIdentity, enabled bool) (Account, error) { + record, err := s.reconcileAccount(ctx, identity) + if err != nil { + return Account{}, err + } + + updated, err := s.repository.SetProviderEnabled(ctx, record.InternalUserID, enabled) + if err != nil { + return Account{}, ErrUnavailable + } + return accountFromRecord(updated), nil +} + +func (s service) reconcileAccount(ctx context.Context, identity users.VerifiedIdentity) (Record, error) { + if s.identities == nil || s.repository == nil { + return Record{}, ErrUnavailable + } + + internalUser, _, err := s.identities.Reconcile(ctx, identity) + if err != nil { + if errors.Is(err, users.ErrInvalidIdentity) { + return Record{}, ErrInvalidIdentity + } + return Record{}, ErrUnavailable + } + + existing, found, err := s.repository.FindByInternalUserID(ctx, internalUser.ID) + if err != nil { + return Record{}, ErrUnavailable + } + if found { + return existing, nil + } + + created, err := s.repository.Create(ctx, internalUser.ID) + if err == nil { + return created, nil + } + if !errors.Is(err, ErrAccountConflict) { + return Record{}, ErrUnavailable + } + + winner, found, err := s.repository.FindByInternalUserID(ctx, internalUser.ID) + if err != nil || !found { + return Record{}, ErrUnavailable + } + return winner, nil +} diff --git a/backend/internal/accounts/service_test.go b/backend/internal/accounts/service_test.go new file mode 100644 index 0000000..d9d80a4 --- /dev/null +++ b/backend/internal/accounts/service_test.go @@ -0,0 +1,234 @@ +package accounts + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" + "github.com/google/uuid" +) + +func TestServiceRejectsInvalidVerifiedIdentityBeforeAccountRepositoryAccess(t *testing.T) { + t.Parallel() + + reconciler := &recordingReconciler{err: users.ErrInvalidIdentity} + repository := &recordingRepository{} + + _, err := NewService(reconciler, repository).Get(context.Background(), users.VerifiedIdentity{}) + + if !errors.Is(err, ErrInvalidIdentity) { + t.Fatalf("error = %v, want ErrInvalidIdentity", err) + } + if repository.findCalls != 0 || repository.createCalls != 0 || repository.updateCalls != 0 { + t.Fatalf("repository calls = find:%d create:%d update:%d, want none", repository.findCalls, repository.createCalls, repository.updateCalls) + } +} + +func TestServiceGetCreatesFirstAccountWithImplicitCustomerCapability(t *testing.T) { + t.Parallel() + + internalUser := testInternalUser() + record := testRecord(internalUser.ID, false) + repository := &recordingRepository{createResult: record} + + account, err := NewService(&recordingReconciler{user: internalUser}, repository).Get( + context.Background(), + users.VerifiedIdentity{Subject: "user_123"}, + ) + + if err != nil { + t.Fatalf("get account: %v", err) + } + assertAccountMatchesRecord(t, account, record) + if !account.CustomerEnabled { + t.Fatal("customer capability must always be enabled") + } + if repository.findCalls != 1 || repository.createCalls != 1 { + t.Fatalf("repository calls = find:%d create:%d, want 1:1", repository.findCalls, repository.createCalls) + } +} + +func TestServiceGetReturnsExistingAccountWithoutCreate(t *testing.T) { + t.Parallel() + + internalUser := testInternalUser() + record := testRecord(internalUser.ID, true) + repository := &recordingRepository{findResults: []accountFindResult{{record: record, found: true}}} + + account, err := NewService(&recordingReconciler{user: internalUser}, repository).Get( + context.Background(), + users.VerifiedIdentity{Subject: "user_123"}, + ) + + if err != nil { + t.Fatalf("get account: %v", err) + } + assertAccountMatchesRecord(t, account, record) + if repository.createCalls != 0 { + t.Fatalf("create calls = %d, want 0", repository.createCalls) + } +} + +func TestServiceGetReloadsConcurrentCreationWinner(t *testing.T) { + t.Parallel() + + internalUser := testInternalUser() + winner := testRecord(internalUser.ID, false) + repository := &recordingRepository{ + findResults: []accountFindResult{{}, {record: winner, found: true}}, + createErr: ErrAccountConflict, + } + + account, err := NewService(&recordingReconciler{user: internalUser}, repository).Get( + context.Background(), + users.VerifiedIdentity{Subject: "user_123"}, + ) + + if err != nil { + t.Fatalf("get account: %v", err) + } + assertAccountMatchesRecord(t, account, winner) + if repository.findCalls != 2 || repository.createCalls != 1 { + t.Fatalf("repository calls = find:%d create:%d, want 2:1", repository.findCalls, repository.createCalls) + } +} + +func TestServiceSetProviderEnabledChangesCapabilityBothDirections(t *testing.T) { + t.Parallel() + + internalUser := testInternalUser() + initial := testRecord(internalUser.ID, false) + enabled := initial + enabled.ProviderEnabled = true + disabled := enabled + disabled.ProviderEnabled = false + repository := &recordingRepository{ + findResults: []accountFindResult{{record: initial, found: true}, {record: enabled, found: true}}, + updateResults: []Record{enabled, disabled}, + } + service := NewService(&recordingReconciler{user: internalUser}, repository) + identity := users.VerifiedIdentity{Subject: "user_123"} + + account, err := service.SetProviderEnabled(context.Background(), identity, true) + if err != nil { + t.Fatalf("enable provider capability: %v", err) + } + assertAccountMatchesRecord(t, account, enabled) + + account, err = service.SetProviderEnabled(context.Background(), identity, false) + if err != nil { + t.Fatalf("disable provider capability: %v", err) + } + assertAccountMatchesRecord(t, account, disabled) + + if repository.updateCalls != 2 || repository.updateValues[0] != true || repository.updateValues[1] != false { + t.Fatalf("updates = calls:%d values:%v, want 2 [true false]", repository.updateCalls, repository.updateValues) + } +} + +func TestServiceMapsRepositoryFailureToUnavailable(t *testing.T) { + t.Parallel() + + internalUser := testInternalUser() + repository := &recordingRepository{findResults: []accountFindResult{{err: errors.New("database unavailable")}}} + + _, err := NewService(&recordingReconciler{user: internalUser}, repository).Get( + context.Background(), + users.VerifiedIdentity{Subject: "user_123"}, + ) + + if !errors.Is(err, ErrUnavailable) { + t.Fatalf("error = %v, want ErrUnavailable", err) + } + if repository.createCalls != 0 || repository.updateCalls != 0 { + t.Fatalf("mutating calls = create:%d update:%d, want none", repository.createCalls, repository.updateCalls) + } +} + +func testInternalUser() users.InternalUser { + return users.InternalUser{ + ID: uuid.MustParse("5809af0d-3cf5-45ac-b120-b45e23a675a4"), + CreatedAt: time.Date(2026, 8, 23, 12, 0, 0, 0, time.UTC), + } +} + +func testRecord(internalUserID uuid.UUID, providerEnabled bool) Record { + return Record{ + InternalUserID: internalUserID, + ProviderEnabled: providerEnabled, + OnboardingCompletedAt: time.Date(2026, 8, 23, 12, 5, 0, 0, time.UTC), + CreatedAt: time.Date(2026, 8, 23, 12, 5, 0, 0, time.UTC), + UpdatedAt: time.Date(2026, 8, 23, 12, 5, 0, 0, time.UTC), + } +} + +func assertAccountMatchesRecord(t *testing.T, account Account, record Record) { + t.Helper() + if account != (Account{ + CustomerEnabled: true, + ProviderEnabled: record.ProviderEnabled, + OnboardingCompletedAt: record.OnboardingCompletedAt, + }) { + t.Fatalf("account = %#v, want record-derived account for %#v", account, record) + } +} + +type recordingReconciler struct { + user users.InternalUser + err error + calls int +} + +func (r *recordingReconciler) Reconcile(_ context.Context, _ users.VerifiedIdentity) (users.InternalUser, bool, error) { + r.calls++ + return r.user, false, r.err +} + +type accountFindResult struct { + record Record + found bool + err error +} + +type recordingRepository struct { + findResults []accountFindResult + createResult Record + createErr error + updateResults []Record + updateErr error + findCalls int + createCalls int + updateCalls int + updateValues []bool +} + +func (r *recordingRepository) FindByInternalUserID(_ context.Context, _ uuid.UUID) (Record, bool, error) { + r.findCalls++ + if len(r.findResults) == 0 { + return Record{}, false, nil + } + result := r.findResults[0] + r.findResults = r.findResults[1:] + return result.record, result.found, result.err +} + +func (r *recordingRepository) Create(_ context.Context, _ uuid.UUID) (Record, error) { + r.createCalls++ + return r.createResult, r.createErr +} + +func (r *recordingRepository) SetProviderEnabled(_ context.Context, _ uuid.UUID, enabled bool) (Record, error) { + r.updateCalls++ + r.updateValues = append(r.updateValues, enabled) + if r.updateErr != nil { + return Record{}, r.updateErr + } + if len(r.updateResults) == 0 { + return Record{}, nil + } + result := r.updateResults[0] + r.updateResults = r.updateResults[1:] + return result, nil +} From fe6295172248d1234ab83887c813ad7738f6e6f7 Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 15:04:00 +0100 Subject: [PATCH 08/64] feat: add protected account capability API --- backend/cmd/api/main.go | 4 +- backend/internal/httpapi/account_handler.go | 105 ++++++++ .../internal/httpapi/account_handler_test.go | 232 ++++++++++++++++++ backend/internal/httpapi/health_handler.go | 3 +- .../internal/httpapi/openapi_contract_test.go | 33 +++ backend/internal/httpapi/router_test.go | 31 ++- frontend/src/shared/api/generated/index.ts | 4 +- frontend/src/shared/api/generated/sdk.gen.ts | 24 +- .../src/shared/api/generated/types.gen.ts | 86 ++++++- openapi/juntly-api.v1.yaml | 111 +++++++++ 10 files changed, 626 insertions(+), 7 deletions(-) create mode 100644 backend/internal/httpapi/account_handler.go create mode 100644 backend/internal/httpapi/account_handler_test.go diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index c94bfc7..1fa02fc 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -15,6 +15,7 @@ import ( "entgo.io/ent/dialect" entsql "entgo.io/ent/dialect/sql" "github.com/SourceSenseiTheRealOne/juntly/backend/ent" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/accounts" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/health" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/httpapi" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" @@ -92,5 +93,6 @@ func newAPIHandler(config runtimeConfig) (http.Handler, io.Closer, error) { healthService := health.NewService(version, time.Now) userService := users.NewService(users.NewEntRepository(client)) - return httpapi.NewRouter(healthService, config.verifier, userService), client, nil + accountService := accounts.NewService(userService, accounts.NewEntRepository(client)) + return httpapi.NewRouter(healthService, config.verifier, userService, accountService), client, nil } diff --git a/backend/internal/httpapi/account_handler.go b/backend/internal/httpapi/account_handler.go new file mode 100644 index 0000000..524990b --- /dev/null +++ b/backend/internal/httpapi/account_handler.go @@ -0,0 +1,105 @@ +package httpapi + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/accounts" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/authn" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" +) + +const maxAccountRequestBytes = 1024 + +type AccountService interface { + Get(context.Context, users.VerifiedIdentity) (accounts.Account, error) + SetProviderEnabled(context.Context, users.VerifiedIdentity, bool) (accounts.Account, error) +} + +type accountHandler struct { + service AccountService +} + +type accountCapabilitiesResponse struct { + CustomerEnabled bool `json:"customerEnabled"` + ProviderEnabled bool `json:"providerEnabled"` + OnboardingCompletedAt string `json:"onboardingCompletedAt"` +} + +type updateAccountCapabilitiesRequest struct { + ProviderEnabled *bool `json:"providerEnabled"` +} + +func NewAccountHandler(service AccountService) http.Handler { + return accountHandler{service: service} +} + +func (h accountHandler) ServeHTTP(w http.ResponseWriter, request *http.Request) { + if request.Method != http.MethodGet && request.Method != http.MethodPut { + w.Header().Set("Allow", "GET, PUT") + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + + requestID := requestIDFromHeader(request.Header.Get(RequestIDHeader)) + identity, ok := authn.IdentityFromContext(request.Context()) + if !ok { + writeAPIError(w, http.StatusUnauthorized, "UNAUTHORIZED", "Unauthorized", requestID) + return + } + if h.service == nil { + writeAPIError(w, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "Service unavailable", requestID) + return + } + + var ( + account accounts.Account + err error + ) + switch request.Method { + case http.MethodGet: + account, err = h.service.Get(request.Context(), identity) + case http.MethodPut: + enabled, valid := decodeProviderEnabled(request.Body) + if !valid { + writeAPIError(w, http.StatusBadRequest, "INVALID_REQUEST", "Invalid request", requestID) + return + } + account, err = h.service.SetProviderEnabled(request.Context(), identity, enabled) + } + + if err != nil { + if errors.Is(err, accounts.ErrInvalidIdentity) { + writeAPIError(w, http.StatusUnauthorized, "UNAUTHORIZED", "Unauthorized", requestID) + return + } + writeAPIError(w, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "Service unavailable", requestID) + return + } + + writeJSON(w, http.StatusOK, accountCapabilitiesResponse{ + CustomerEnabled: account.CustomerEnabled, + ProviderEnabled: account.ProviderEnabled, + OnboardingCompletedAt: account.OnboardingCompletedAt.UTC().Format(time.RFC3339Nano), + }, requestID) +} + +func decodeProviderEnabled(body io.Reader) (bool, bool) { + decoder := json.NewDecoder(io.LimitReader(body, maxAccountRequestBytes+1)) + decoder.DisallowUnknownFields() + + var payload updateAccountCapabilitiesRequest + if err := decoder.Decode(&payload); err != nil || payload.ProviderEnabled == nil { + return false, false + } + + var extra any + if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) { + return false, false + } + return *payload.ProviderEnabled, true +} diff --git a/backend/internal/httpapi/account_handler_test.go b/backend/internal/httpapi/account_handler_test.go new file mode 100644 index 0000000..c75fc52 --- /dev/null +++ b/backend/internal/httpapi/account_handler_test.go @@ -0,0 +1,232 @@ +package httpapi_test + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/accounts" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/authn" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/httpapi" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" +) + +func TestAccountHandlerReturnsUnauthorizedWithoutVerifiedIdentity(t *testing.T) { + t.Parallel() + + for _, method := range []string{http.MethodGet, http.MethodPut} { + method := method + t.Run(method, func(t *testing.T) { + t.Parallel() + service := &recordingAccountService{} + response := httptest.NewRecorder() + request := httptest.NewRequest(method, "/api/v1/me/account", strings.NewReader(`{"providerEnabled":true}`)) + request.Header.Set(httpapi.RequestIDHeader, "req_account_unauthorized") + + httpapi.NewAccountHandler(service).ServeHTTP(response, request) + + if response.Code != http.StatusUnauthorized { + t.Fatalf("status = %d, want %d", response.Code, http.StatusUnauthorized) + } + if service.calls != 0 { + t.Fatalf("service calls = %d, want 0", service.calls) + } + assertErrorResponse(t, response, "UNAUTHORIZED", "Unauthorized", "req_account_unauthorized") + }) + } +} + +func TestAccountHandlerGETReturnsClosedCapabilities(t *testing.T) { + t.Parallel() + + completedAt := time.Date(2026, 8, 23, 12, 5, 0, 123456000, time.UTC) + service := &recordingAccountService{account: accounts.Account{ + CustomerEnabled: true, + ProviderEnabled: false, + OnboardingCompletedAt: completedAt, + }} + handler := authn.RequireVerifiedIdentity( + staticVerifier{identity: users.VerifiedIdentity{Subject: "user_synthetic"}}, + httpapi.NewAccountHandler(service), + ) + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/api/v1/me/account", nil) + request.Header.Set("Authorization", "Bearer synthetic-token") + request.Header.Set(httpapi.RequestIDHeader, "req_account_get") + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusOK { + t.Fatalf("status = %d, want %d", response.Code, http.StatusOK) + } + if response.Header().Get(httpapi.RequestIDHeader) != "req_account_get" { + t.Fatalf("request ID header = %q", response.Header().Get(httpapi.RequestIDHeader)) + } + body := decodeJSONMap(t, response) + want := map[string]any{ + "customerEnabled": true, + "providerEnabled": false, + "onboardingCompletedAt": "2026-08-23T12:05:00.123456Z", + } + if mustJSON(t, body) != mustJSON(t, want) { + t.Fatalf("body = %#v, want %#v", body, want) + } + serialized := mustJSON(t, body) + for _, prohibited := range []string{"user_synthetic", "synthetic-token", "internalUserId", "clerk"} { + if strings.Contains(serialized, prohibited) { + t.Fatalf("response leaks %q: %s", prohibited, serialized) + } + } +} + +func TestAccountHandlerPUTUpdatesProviderCapability(t *testing.T) { + t.Parallel() + + for _, enabled := range []bool{true, false} { + enabled := enabled + t.Run(map[bool]string{true: "enable", false: "disable"}[enabled], func(t *testing.T) { + t.Parallel() + service := &recordingAccountService{account: accounts.Account{ + CustomerEnabled: true, + ProviderEnabled: enabled, + OnboardingCompletedAt: time.Date(2026, 8, 23, 12, 5, 0, 0, time.UTC), + }} + handler := authn.RequireVerifiedIdentity( + staticVerifier{identity: users.VerifiedIdentity{Subject: "user_synthetic"}}, + httpapi.NewAccountHandler(service), + ) + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPut, "/api/v1/me/account", strings.NewReader(map[bool]string{true: `{"providerEnabled":true}`, false: `{"providerEnabled":false}`}[enabled])) + request.Header.Set("Authorization", "Bearer synthetic-token") + request.Header.Set(httpapi.RequestIDHeader, "req_account_put") + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusOK { + t.Fatalf("status = %d, want %d", response.Code, http.StatusOK) + } + if service.calls != 1 || service.providerEnabled == nil || *service.providerEnabled != enabled { + t.Fatalf("service calls=%d providerEnabled=%v, want 1 %t", service.calls, service.providerEnabled, enabled) + } + if service.identity.Subject != "user_synthetic" { + t.Fatalf("service subject = %q", service.identity.Subject) + } + }) + } +} + +func TestAccountHandlerPUTRejectsNonExactJSONBeforeService(t *testing.T) { + t.Parallel() + + cases := map[string]string{ + "empty body": "", + "empty object": `{}`, + "null body": `null`, + "unknown property": `{"providerEnabled":true,"admin":true}`, + "non boolean": `{"providerEnabled":"true"}`, + "null capability": `{"providerEnabled":null}`, + "trailing bytes": `{"providerEnabled":true} trailing`, + "second value": `{"providerEnabled":true}{}`, + } + for name, body := range cases { + name, body := name, body + t.Run(name, func(t *testing.T) { + t.Parallel() + service := &recordingAccountService{} + handler := authn.RequireVerifiedIdentity( + staticVerifier{identity: users.VerifiedIdentity{Subject: "user_synthetic"}}, + httpapi.NewAccountHandler(service), + ) + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPut, "/api/v1/me/account", strings.NewReader(body)) + request.Header.Set("Authorization", "Bearer synthetic-token") + request.Header.Set(httpapi.RequestIDHeader, "req_account_invalid") + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusBadRequest { + t.Fatalf("status = %d, want %d", response.Code, http.StatusBadRequest) + } + if service.calls != 0 { + t.Fatalf("service calls = %d, want 0", service.calls) + } + assertErrorResponse(t, response, "INVALID_REQUEST", "Invalid request", "req_account_invalid") + }) + } +} + +func TestAccountHandlerReturnsSafeUnavailable(t *testing.T) { + t.Parallel() + + service := &recordingAccountService{err: errors.New("database at internal-host failed for user_synthetic")} + handler := authn.RequireVerifiedIdentity( + staticVerifier{identity: users.VerifiedIdentity{Subject: "user_synthetic"}}, + httpapi.NewAccountHandler(service), + ) + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/api/v1/me/account", nil) + request.Header.Set("Authorization", "Bearer synthetic-token") + request.Header.Set(httpapi.RequestIDHeader, "req_account_unavailable") + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusServiceUnavailable { + t.Fatalf("status = %d, want %d", response.Code, http.StatusServiceUnavailable) + } + assertErrorResponse(t, response, "SERVICE_UNAVAILABLE", "Service unavailable", "req_account_unavailable") +} + +func TestAccountHandlerRejectsOtherMethods(t *testing.T) { + t.Parallel() + + service := &recordingAccountService{} + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/api/v1/me/account", nil) + + httpapi.NewAccountHandler(service).ServeHTTP(response, request) + + if response.Code != http.StatusMethodNotAllowed { + t.Fatalf("status = %d, want %d", response.Code, http.StatusMethodNotAllowed) + } + if allow := response.Header().Get("Allow"); allow != "GET, PUT" { + t.Fatalf("Allow = %q, want %q", allow, "GET, PUT") + } + if service.calls != 0 { + t.Fatalf("service calls = %d, want 0", service.calls) + } +} + +type recordingAccountService struct { + account accounts.Account + err error + calls int + identity users.VerifiedIdentity + providerEnabled *bool +} + +func (s *recordingAccountService) Get(_ context.Context, identity users.VerifiedIdentity) (accounts.Account, error) { + s.calls++ + s.identity = identity + return s.account, s.err +} + +func (s *recordingAccountService) SetProviderEnabled(_ context.Context, identity users.VerifiedIdentity, enabled bool) (accounts.Account, error) { + s.calls++ + s.identity = identity + s.providerEnabled = &enabled + return s.account, s.err +} + +func decodeJSONMap(t *testing.T, response *httptest.ResponseRecorder) map[string]any { + t.Helper() + var body map[string]any + if err := json.NewDecoder(response.Body).Decode(&body); err != nil { + t.Fatalf("decode response: %v", err) + } + return body +} diff --git a/backend/internal/httpapi/health_handler.go b/backend/internal/httpapi/health_handler.go index dae1ad5..2b72d5b 100644 --- a/backend/internal/httpapi/health_handler.go +++ b/backend/internal/httpapi/health_handler.go @@ -35,10 +35,11 @@ func (h HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(h.service.Check(requestID)) } -func NewRouter(service health.Service, verifier authn.Verifier, reconcileService ReconcileService) http.Handler { +func NewRouter(service health.Service, verifier authn.Verifier, reconcileService ReconcileService, accountService AccountService) http.Handler { mux := http.NewServeMux() mux.Handle("/api/v1/health", NewHealthHandler(service)) mux.Handle("/api/v1/auth/reconcile", authn.RequireVerifiedIdentity(verifier, NewReconcileHandler(reconcileService))) + mux.Handle("/api/v1/me/account", authn.RequireVerifiedIdentity(verifier, NewAccountHandler(accountService))) return mux } diff --git a/backend/internal/httpapi/openapi_contract_test.go b/backend/internal/httpapi/openapi_contract_test.go index e199c74..3440aeb 100644 --- a/backend/internal/httpapi/openapi_contract_test.go +++ b/backend/internal/httpapi/openapi_contract_test.go @@ -37,3 +37,36 @@ func TestReconcileOpenAPIContract(t *testing.T) { t.Fatal("reconciliation operation must not accept a request body") } } + +func TestAccountCapabilitiesOpenAPIContract(t *testing.T) { + t.Parallel() + + contract, err := os.ReadFile("../../../openapi/juntly-api.v1.yaml") + if err != nil { + t.Fatalf("read OpenAPI contract: %v", err) + } + contents := string(contract) + + for _, required := range []string{ + "/api/v1/me/account:", + "operationId: getAccountCapabilities", + "operationId: updateAccountCapabilities", + "AccountCapabilitiesResponse:", + "UpdateAccountCapabilitiesRequest:", + "customerEnabled:", + "const: true", + "providerEnabled:", + "onboardingCompletedAt:", + "INVALID_REQUEST", + "clerkSession: []", + } { + if !strings.Contains(contents, required) { + t.Fatalf("OpenAPI contract does not contain %q", required) + } + } + + accountPath := contents[strings.Index(contents, "/api/v1/me/account:"):] + if !strings.Contains(accountPath, "get:") || !strings.Contains(accountPath, "put:") { + t.Fatal("account contract must declare GET and PUT") + } +} diff --git a/backend/internal/httpapi/router_test.go b/backend/internal/httpapi/router_test.go index 4421bc5..2bf52f1 100644 --- a/backend/internal/httpapi/router_test.go +++ b/backend/internal/httpapi/router_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/accounts" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/health" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/httpapi" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" @@ -24,7 +25,12 @@ func TestRouterLeavesHealthPublicAndProtectsReconciliation(t *testing.T) { healthService := health.NewService("0.1.0", func() time.Time { return time.Date(2026, 8, 21, 12, 0, 0, 0, time.UTC) }) - router := httpapi.NewRouter(healthService, verifier, reconcileService) + accountService := &recordingAccountService{account: accounts.Account{ + CustomerEnabled: true, + ProviderEnabled: false, + OnboardingCompletedAt: time.Date(2026, 8, 23, 12, 5, 0, 0, time.UTC), + }} + router := httpapi.NewRouter(healthService, verifier, reconcileService, accountService) healthResponse := httptest.NewRecorder() router.ServeHTTP(healthResponse, httptest.NewRequest(http.MethodGet, "/api/v1/health", nil)) @@ -54,6 +60,29 @@ func TestRouterLeavesHealthPublicAndProtectsReconciliation(t *testing.T) { if verifier.calls != 1 { t.Fatalf("verifier calls after valid bearer = %d, want 1", verifier.calls) } + + unauthorizedAccountResponse := httptest.NewRecorder() + router.ServeHTTP(unauthorizedAccountResponse, httptest.NewRequest(http.MethodGet, "/api/v1/me/account", nil)) + if unauthorizedAccountResponse.Code != http.StatusUnauthorized { + t.Fatalf("unauthorized account status = %d, want %d", unauthorizedAccountResponse.Code, http.StatusUnauthorized) + } + if accountService.calls != 0 { + t.Fatalf("account service calls after missing bearer = %d, want 0", accountService.calls) + } + + authorizedAccountRequest := httptest.NewRequest(http.MethodGet, "/api/v1/me/account", nil) + authorizedAccountRequest.Header.Set("Authorization", "Bearer synthetic-token") + authorizedAccountResponse := httptest.NewRecorder() + router.ServeHTTP(authorizedAccountResponse, authorizedAccountRequest) + if authorizedAccountResponse.Code != http.StatusOK { + t.Fatalf("authorized account status = %d, want %d", authorizedAccountResponse.Code, http.StatusOK) + } + if verifier.calls != 2 { + t.Fatalf("verifier calls after account bearer = %d, want 2", verifier.calls) + } + if accountService.calls != 1 { + t.Fatalf("account service calls = %d, want 1", accountService.calls) + } } type routerVerifier struct { diff --git a/frontend/src/shared/api/generated/index.ts b/frontend/src/shared/api/generated/index.ts index bcccb74..84abeca 100644 --- a/frontend/src/shared/api/generated/index.ts +++ b/frontend/src/shared/api/generated/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { getHealth, type Options, reconcileInternalUser } from './sdk.gen'; -export type { ClientOptions, ErrorCode, ErrorDetail, ErrorResponse, GetHealthData, GetHealthError, GetHealthErrors, GetHealthResponse, GetHealthResponses, HealthResponse, HealthStatus, InternalUserResponse, ReconcileInternalUserData, ReconcileInternalUserError, ReconcileInternalUserErrors, ReconcileInternalUserResponse, ReconcileInternalUserResponses, RequestId, RequestIdHeader } from './types.gen'; +export { getAccountCapabilities, getHealth, type Options, reconcileInternalUser, updateAccountCapabilities } from './sdk.gen'; +export type { AccountCapabilitiesResponse, ClientOptions, ErrorCode, ErrorDetail, ErrorResponse, GetAccountCapabilitiesData, GetAccountCapabilitiesError, GetAccountCapabilitiesErrors, GetAccountCapabilitiesResponse, GetAccountCapabilitiesResponses, GetHealthData, GetHealthError, GetHealthErrors, GetHealthResponse, GetHealthResponses, HealthResponse, HealthStatus, InternalUserResponse, ReconcileInternalUserData, ReconcileInternalUserError, ReconcileInternalUserErrors, ReconcileInternalUserResponse, ReconcileInternalUserResponses, RequestId, RequestIdHeader, UpdateAccountCapabilitiesData, UpdateAccountCapabilitiesError, UpdateAccountCapabilitiesErrors, UpdateAccountCapabilitiesRequest, UpdateAccountCapabilitiesResponse, UpdateAccountCapabilitiesResponses } from './types.gen'; diff --git a/frontend/src/shared/api/generated/sdk.gen.ts b/frontend/src/shared/api/generated/sdk.gen.ts index 89e0b60..afa7d30 100644 --- a/frontend/src/shared/api/generated/sdk.gen.ts +++ b/frontend/src/shared/api/generated/sdk.gen.ts @@ -2,7 +2,7 @@ import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; -import type { GetHealthData, GetHealthErrors, GetHealthResponses, ReconcileInternalUserData, ReconcileInternalUserErrors, ReconcileInternalUserResponses } from './types.gen'; +import type { GetAccountCapabilitiesData, GetAccountCapabilitiesErrors, GetAccountCapabilitiesResponses, GetHealthData, GetHealthErrors, GetHealthResponses, ReconcileInternalUserData, ReconcileInternalUserErrors, ReconcileInternalUserResponses, UpdateAccountCapabilitiesData, UpdateAccountCapabilitiesErrors, UpdateAccountCapabilitiesResponses } from './types.gen'; export type Options = Options2 & { /** @@ -25,6 +25,28 @@ export type Options(options?: Options) => (options?.client ?? client).get({ url: '/api/v1/health', ...options }); +/** + * Read the current user's account capabilities. + */ +export const getAccountCapabilities = (options?: Options) => (options?.client ?? client).get({ + security: [{ scheme: 'bearer', type: 'http' }], + url: '/api/v1/me/account', + ...options +}); + +/** + * Update the current user's provider capability. + */ +export const updateAccountCapabilities = (options: Options) => (options.client ?? client).put({ + security: [{ scheme: 'bearer', type: 'http' }], + url: '/api/v1/me/account', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + /** * Reconcile the current verified session to an internal user. * diff --git a/frontend/src/shared/api/generated/types.gen.ts b/frontend/src/shared/api/generated/types.gen.ts index fc15678..168dc8e 100644 --- a/frontend/src/shared/api/generated/types.gen.ts +++ b/frontend/src/shared/api/generated/types.gen.ts @@ -16,7 +16,7 @@ export type HealthResponse = { requestId: RequestId; }; -export type ErrorCode = 'UNAUTHORIZED' | 'SERVICE_UNAVAILABLE'; +export type ErrorCode = 'INVALID_REQUEST' | 'UNAUTHORIZED' | 'SERVICE_UNAVAILABLE'; export type ErrorDetail = { code: ErrorCode; @@ -33,6 +33,16 @@ export type InternalUserResponse = { createdAt: string; }; +export type AccountCapabilitiesResponse = { + customerEnabled: true; + providerEnabled: boolean; + onboardingCompletedAt: string; +}; + +export type UpdateAccountCapabilitiesRequest = { + providerEnabled: boolean; +}; + /** * Optional client-supplied correlation identifier. */ @@ -69,6 +79,80 @@ export type GetHealthResponses = { export type GetHealthResponse = GetHealthResponses[keyof GetHealthResponses]; +export type GetAccountCapabilitiesData = { + body?: never; + headers?: { + /** + * Optional client-supplied correlation identifier. + */ + 'X-Request-ID'?: RequestId; + }; + path?: never; + query?: never; + url: '/api/v1/me/account'; +}; + +export type GetAccountCapabilitiesErrors = { + /** + * Session authorization is missing or invalid. + */ + 401: ErrorResponse; + /** + * A required service dependency is unavailable. + */ + 503: ErrorResponse; +}; + +export type GetAccountCapabilitiesError = GetAccountCapabilitiesErrors[keyof GetAccountCapabilitiesErrors]; + +export type GetAccountCapabilitiesResponses = { + /** + * Account capabilities are available. + */ + 200: AccountCapabilitiesResponse; +}; + +export type GetAccountCapabilitiesResponse = GetAccountCapabilitiesResponses[keyof GetAccountCapabilitiesResponses]; + +export type UpdateAccountCapabilitiesData = { + body: UpdateAccountCapabilitiesRequest; + headers?: { + /** + * Optional client-supplied correlation identifier. + */ + 'X-Request-ID'?: RequestId; + }; + path?: never; + query?: never; + url: '/api/v1/me/account'; +}; + +export type UpdateAccountCapabilitiesErrors = { + /** + * The request body is invalid. + */ + 400: ErrorResponse; + /** + * Session authorization is missing or invalid. + */ + 401: ErrorResponse; + /** + * A required service dependency is unavailable. + */ + 503: ErrorResponse; +}; + +export type UpdateAccountCapabilitiesError = UpdateAccountCapabilitiesErrors[keyof UpdateAccountCapabilitiesErrors]; + +export type UpdateAccountCapabilitiesResponses = { + /** + * Account capabilities were updated. + */ + 200: AccountCapabilitiesResponse; +}; + +export type UpdateAccountCapabilitiesResponse = UpdateAccountCapabilitiesResponses[keyof UpdateAccountCapabilitiesResponses]; + export type ReconcileInternalUserData = { body?: never; headers?: { diff --git a/openapi/juntly-api.v1.yaml b/openapi/juntly-api.v1.yaml index 1090e4c..285be71 100644 --- a/openapi/juntly-api.v1.yaml +++ b/openapi/juntly-api.v1.yaml @@ -33,6 +33,92 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorResponse" + /api/v1/me/account: + get: + operationId: getAccountCapabilities + summary: Read the current user's account capabilities. + security: + - clerkSession: [] + parameters: + - $ref: "#/components/parameters/RequestIdHeader" + responses: + "200": + description: Account capabilities are available. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/AccountCapabilitiesResponse" + "401": + description: Session authorization is missing or invalid. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "503": + description: A required service dependency is unavailable. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + put: + operationId: updateAccountCapabilities + summary: Update the current user's provider capability. + security: + - clerkSession: [] + parameters: + - $ref: "#/components/parameters/RequestIdHeader" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateAccountCapabilitiesRequest" + responses: + "200": + description: Account capabilities were updated. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/AccountCapabilitiesResponse" + "400": + description: The request body is invalid. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "401": + description: Session authorization is missing or invalid. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "503": + description: A required service dependency is unavailable. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" /api/v1/auth/reconcile: post: operationId: reconcileInternalUser @@ -129,6 +215,7 @@ components: ErrorCode: type: string enum: + - INVALID_REQUEST - UNAUTHORIZED - SERVICE_UNAVAILABLE ErrorDetail: @@ -167,3 +254,27 @@ components: createdAt: type: string format: date-time + AccountCapabilitiesResponse: + type: object + additionalProperties: false + required: + - customerEnabled + - providerEnabled + - onboardingCompletedAt + properties: + customerEnabled: + type: boolean + const: true + providerEnabled: + type: boolean + onboardingCompletedAt: + type: string + format: date-time + UpdateAccountCapabilitiesRequest: + type: object + additionalProperties: false + required: + - providerEnabled + properties: + providerEnabled: + type: boolean From 96e023378130d1fd4383d9efb00c60f27c86de14 Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 15:09:15 +0100 Subject: [PATCH 09/64] feat: add same-origin account capability BFF --- .../src/app/api/v1/me/account/route.test.ts | 247 ++++++++++++++++++ frontend/src/app/api/v1/me/account/route.ts | 205 +++++++++++++++ 2 files changed, 452 insertions(+) create mode 100644 frontend/src/app/api/v1/me/account/route.test.ts create mode 100644 frontend/src/app/api/v1/me/account/route.ts diff --git a/frontend/src/app/api/v1/me/account/route.test.ts b/frontend/src/app/api/v1/me/account/route.test.ts new file mode 100644 index 0000000..37d0ba9 --- /dev/null +++ b/frontend/src/app/api/v1/me/account/route.test.ts @@ -0,0 +1,247 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + auth: vi.fn(), +})); + +vi.mock("@clerk/nextjs/server", () => ({ auth: mocks.auth })); + +import { GET, PUT } from "./route"; + +describe("/api/v1/me/account BFF route", () => { + beforeEach(() => { + vi.stubEnv("JUNTLY_API_ORIGIN", "http://go-api:8080"); + }); + + afterEach(() => { + mocks.auth.mockReset(); + vi.unstubAllEnvs(); + vi.restoreAllMocks(); + }); + + it.each([ + ["GET", GET, undefined], + ["PUT", PUT, { providerEnabled: true }], + ])( + "returns 401 for signed-out %s without calling upstream", + async (_, handler, body) => { + mocks.auth.mockResolvedValue({ + isAuthenticated: false, + getToken: vi.fn(), + }); + const upstreamFetch = vi.fn(); + vi.stubGlobal("fetch", upstreamFetch); + + const response = await handler( + request(body, "req_account_signed_out", body ? "PUT" : "GET"), + ); + + expect(response.status).toBe(401); + await expect(response.json()).resolves.toEqual({ + error: { + code: "UNAUTHORIZED", + message: "Unauthorized", + requestId: "req_account_signed_out", + }, + }); + expect(upstreamFetch).not.toHaveBeenCalled(); + }, + ); + + it("returns 401 when the Clerk session has no current token", async () => { + mocks.auth.mockResolvedValue({ + isAuthenticated: true, + getToken: vi.fn().mockResolvedValue(null), + }); + const upstreamFetch = vi.fn(); + vi.stubGlobal("fetch", upstreamFetch); + + const response = await GET( + request(undefined, "req_account_no_token", "GET"), + ); + + expect(response.status).toBe(401); + expect(upstreamFetch).not.toHaveBeenCalled(); + }); + + it("GET forwards only the server token and correlation ID", async () => { + const getToken = vi.fn().mockResolvedValue("server-obtained-token"); + mocks.auth.mockResolvedValue({ isAuthenticated: true, getToken }); + vi.stubGlobal( + "fetch", + vi.fn(async (upstream: Request) => { + expect(upstream.url).toBe("http://go-api:8080/api/v1/me/account"); + expect(upstream.method).toBe("GET"); + expect(upstream.headers.get("Authorization")).toBe( + "Bearer server-obtained-token", + ); + expect(upstream.headers.get("X-Request-ID")).toBe("req_account_get"); + await expect(upstream.text()).resolves.toBe(""); + return accountResponse(false, "req_account_get"); + }), + ); + + const response = await GET(request(undefined, "req_account_get", "GET")); + + expect(response.status).toBe(200); + expect(response.headers.get("X-Request-ID")).toBe("req_account_get"); + await expect(response.json()).resolves.toEqual({ + customerEnabled: true, + providerEnabled: false, + onboardingCompletedAt: "2026-08-23T12:05:00.123456Z", + }); + expect(getToken).toHaveBeenCalledOnce(); + }); + + it("PUT validates and forwards the exact provider capability body", async () => { + mocks.auth.mockResolvedValue({ + isAuthenticated: true, + getToken: vi.fn().mockResolvedValue("server-obtained-token"), + }); + vi.stubGlobal( + "fetch", + vi.fn(async (upstream: Request) => { + expect(upstream.url).toBe("http://go-api:8080/api/v1/me/account"); + expect(upstream.method).toBe("PUT"); + expect(upstream.headers.get("Authorization")).toBe( + "Bearer server-obtained-token", + ); + expect(upstream.headers.get("X-Request-ID")).toBe("req_account_put"); + await expect(upstream.json()).resolves.toEqual({ + providerEnabled: true, + }); + return accountResponse(true, "req_account_put"); + }), + ); + + const response = await PUT( + request({ providerEnabled: true }, "req_account_put", "PUT"), + ); + + expect(response.status).toBe(200); + await expect(response.json()).resolves.toMatchObject({ + customerEnabled: true, + providerEnabled: true, + }); + }); + + it.each([ + ["empty object", `{}`], + ["null", `null`], + ["unknown property", `{"providerEnabled":true,"admin":true}`], + ["non-boolean", `{"providerEnabled":"true"}`], + ["null capability", `{"providerEnabled":null}`], + ["trailing bytes", `{"providerEnabled":true} trailing`], + ["second value", `{"providerEnabled":true}{}`], + ])("PUT rejects %s before upstream", async (_, body) => { + mocks.auth.mockResolvedValue({ + isAuthenticated: true, + getToken: vi.fn().mockResolvedValue("server-obtained-token"), + }); + const upstreamFetch = vi.fn(); + vi.stubGlobal("fetch", upstreamFetch); + + const response = await PUT( + new Request("http://localhost/api/v1/me/account", { + method: "PUT", + headers: { "X-Request-ID": "req_account_invalid" }, + body, + }), + ); + + expect(response.status).toBe(400); + await expect(response.json()).resolves.toEqual({ + error: { + code: "INVALID_REQUEST", + message: "Invalid request", + requestId: "req_account_invalid", + }, + }); + expect(upstreamFetch).not.toHaveBeenCalled(); + }); + + it.each([ + ["missing origin", "missing-origin"], + ["network failure", "network"], + ["malformed response", "malformed"], + ["extra response property", "extra"], + ["correlation mismatch", "mismatch"], + ])("maps %s to a topology-safe 503", async (_, failure) => { + mocks.auth.mockResolvedValue({ + isAuthenticated: true, + getToken: vi.fn().mockResolvedValue("server-obtained-token"), + }); + if (failure === "missing-origin") { + vi.stubEnv("JUNTLY_API_ORIGIN", ""); + } + vi.stubGlobal( + "fetch", + vi.fn(async () => { + if (failure === "network") { + throw new Error("connect ECONNREFUSED http://go-api:8080"); + } + if (failure === "malformed") { + return Response.json( + { customerEnabled: false, providerEnabled: "yes" }, + { headers: { "X-Request-ID": "req_account_failure" } }, + ); + } + if (failure === "extra") { + return Response.json( + { + customerEnabled: true, + providerEnabled: false, + onboardingCompletedAt: "2026-08-23T12:05:00Z", + internalUserId: "not-public", + }, + { headers: { "X-Request-ID": "req_account_failure" } }, + ); + } + return accountResponse(false, "req_different_correlation"); + }), + ); + + const response = await GET( + request(undefined, "req_account_failure", "GET"), + ); + + expect(response.status).toBe(503); + const body = await response.json(); + expect(body).toEqual({ + error: { + code: "SERVICE_UNAVAILABLE", + message: "Service unavailable", + requestId: "req_account_failure", + }, + }); + expect(JSON.stringify(body)).not.toContain("go-api"); + expect(JSON.stringify(body)).not.toContain("ECONNREFUSED"); + expect(JSON.stringify(body)).not.toContain("internalUserId"); + }); +}); + +function request( + body: { providerEnabled: boolean } | undefined, + requestID: string, + method: "GET" | "PUT", +): Request { + return new Request("http://localhost/api/v1/me/account", { + method, + headers: { "X-Request-ID": requestID }, + body: body ? JSON.stringify(body) : undefined, + }); +} + +function accountResponse( + providerEnabled: boolean, + requestID: string, +): Response { + return Response.json( + { + customerEnabled: true, + providerEnabled, + onboardingCompletedAt: "2026-08-23T12:05:00.123456Z", + }, + { headers: { "X-Request-ID": requestID } }, + ); +} diff --git a/frontend/src/app/api/v1/me/account/route.ts b/frontend/src/app/api/v1/me/account/route.ts new file mode 100644 index 0000000..4744ecd --- /dev/null +++ b/frontend/src/app/api/v1/me/account/route.ts @@ -0,0 +1,205 @@ +import { auth } from "@clerk/nextjs/server"; + +import { + getAccountCapabilities, + updateAccountCapabilities, +} from "@/shared/api/generated"; +import type { + AccountCapabilitiesResponse, + ErrorResponse, + UpdateAccountCapabilitiesRequest, +} from "@/shared/api/generated"; + +const requestIDHeader = "X-Request-ID"; +const unauthorizedMessage = "Unauthorized"; +const invalidRequestMessage = "Invalid request"; +const unavailableMessage = "Service unavailable"; + +export const runtime = "nodejs"; + +export async function GET(request: Request): Promise { + const requestID = readRequestID(request.headers); + const token = await currentSessionToken(); + if (!token) { + return unauthorizedResponse(requestID); + } + + const apiOrigin = process.env.JUNTLY_API_ORIGIN; + if (!apiOrigin) { + return unavailableResponse(requestID); + } + + try { + const upstream = await getAccountCapabilities({ + baseUrl: apiOrigin, + headers: upstreamHeaders(token, requestID), + }); + return accountResponse(upstream, requestID); + } catch { + return unavailableResponse(requestID); + } +} + +export async function PUT(request: Request): Promise { + const requestID = readRequestID(request.headers); + const token = await currentSessionToken(); + if (!token) { + return unauthorizedResponse(requestID); + } + + const body = await readUpdateRequest(request); + if (!body) { + return invalidRequestResponse(requestID); + } + + const apiOrigin = process.env.JUNTLY_API_ORIGIN; + if (!apiOrigin) { + return unavailableResponse(requestID); + } + + try { + const upstream = await updateAccountCapabilities({ + baseUrl: apiOrigin, + body, + headers: upstreamHeaders(token, requestID), + }); + return accountResponse(upstream, requestID); + } catch { + return unavailableResponse(requestID); + } +} + +async function currentSessionToken(): Promise { + try { + const { isAuthenticated, getToken } = await auth(); + if (!isAuthenticated) { + return null; + } + return await getToken(); + } catch { + return null; + } +} + +async function readUpdateRequest( + request: Request, +): Promise { + try { + const value: unknown = JSON.parse(await request.text()); + if (!isExactObject(value, ["providerEnabled"])) { + return null; + } + if (typeof value.providerEnabled !== "boolean") { + return null; + } + return { providerEnabled: value.providerEnabled }; + } catch { + return null; + } +} + +function upstreamHeaders(token: string, requestID: string): HeadersInit { + return { + Authorization: `Bearer ${token}`, + [requestIDHeader]: requestID, + }; +} + +function accountResponse( + upstream: { + data?: AccountCapabilitiesResponse; + error?: unknown; + response?: Response; + }, + requestID: string, +): Response { + if ( + upstream.error || + !upstream.response?.ok || + upstream.response.headers.get(requestIDHeader) !== requestID || + !isAccountCapabilitiesResponse(upstream.data) + ) { + return unavailableResponse(requestID); + } + + return Response.json(upstream.data, { + status: 200, + headers: { [requestIDHeader]: requestID }, + }); +} + +function isAccountCapabilitiesResponse( + value: AccountCapabilitiesResponse | undefined, +): value is AccountCapabilitiesResponse { + return ( + isExactObject(value, [ + "customerEnabled", + "onboardingCompletedAt", + "providerEnabled", + ]) && + value.customerEnabled === true && + typeof value.providerEnabled === "boolean" && + typeof value.onboardingCompletedAt === "string" && + !Number.isNaN(Date.parse(value.onboardingCompletedAt)) + ); +} + +function isExactObject( + value: unknown, + expectedKeys: readonly string[], +): value is Record { + if (value === null || typeof value !== "object" || Array.isArray(value)) { + return false; + } + const keys = Object.keys(value).sort(); + const sortedExpectedKeys = [...expectedKeys].sort(); + return ( + keys.length === sortedExpectedKeys.length && + keys.every((key, index) => key === sortedExpectedKeys[index]) + ); +} + +function unauthorizedResponse(requestID: string): Response { + return errorResponse("UNAUTHORIZED", unauthorizedMessage, 401, requestID); +} + +function invalidRequestResponse(requestID: string): Response { + return errorResponse( + "INVALID_REQUEST", + invalidRequestMessage, + 400, + requestID, + ); +} + +function unavailableResponse(requestID: string): Response { + return errorResponse( + "SERVICE_UNAVAILABLE", + unavailableMessage, + 503, + requestID, + ); +} + +function errorResponse( + code: ErrorResponse["error"]["code"], + message: string, + status: number, + requestID: string, +): Response { + const body: ErrorResponse = { + error: { code, message, requestId: requestID }, + }; + return Response.json(body, { + status, + headers: { [requestIDHeader]: requestID }, + }); +} + +function readRequestID(headers: Headers): string { + const requestID = headers.get(requestIDHeader); + if (requestID && /^[A-Za-z0-9._:-]{8,128}$/.test(requestID)) { + return requestID; + } + return `req_${globalThis.crypto.randomUUID()}`; +} From d2d16eb7e670ad61da6912a2c9142eb624a280ac Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 15:17:39 +0100 Subject: [PATCH 10/64] feat: add account capability onboarding UI --- frontend/messages/en.json | 16 +- frontend/messages/es.json | 16 +- frontend/messages/pt-PT.json | 16 +- .../src/app/[locale]/account/page.test.tsx | 24 ++ frontend/src/app/[locale]/account/page.tsx | 20 +- .../account-capabilities-card.test.tsx | 134 +++++++++ .../account/account-capabilities-card.tsx | 265 ++++++++++++++++++ frontend/src/i18n/messages.test.ts | 24 ++ 8 files changed, 510 insertions(+), 5 deletions(-) create mode 100644 frontend/src/features/account/account-capabilities-card.test.tsx create mode 100644 frontend/src/features/account/account-capabilities-card.tsx diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 3a88187..e667251 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -25,7 +25,21 @@ }, "Account": { "title": "Juntly account", - "description": "Your session is active. Marketplace profiles and features will appear here as they are implemented." + "description": "Your session is active. Choose how you want to use Juntly.", + "capabilities": { + "title": "How you use Juntly", + "description": "Your account can find services and, if you choose, offer them too.", + "customerLabel": "Find services", + "customerDescription": "Your account is always ready to find and contact providers.", + "providerLabel": "Offer services", + "providerDescription": "Enable this option to prepare your provider profile in the next steps.", + "enabled": "Enabled", + "disabled": "Disabled", + "loading": "Loading account capabilities…", + "saving": "Saving…", + "loadError": "We could not load your account capabilities.", + "retry": "Try again" + } }, "Errors": { "title": "Something went wrong", diff --git a/frontend/messages/es.json b/frontend/messages/es.json index 5991f8f..09c3e31 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -25,7 +25,21 @@ }, "Account": { "title": "Cuenta Juntly", - "description": "Tu sesión está activa. Los perfiles y funciones del marketplace aparecerán aquí a medida que se implementen." + "description": "Tu sesión está activa. Elige cómo quieres utilizar Juntly.", + "capabilities": { + "title": "Cómo utilizas Juntly", + "description": "Tu cuenta puede encontrar servicios y, si quieres, también ofrecerlos.", + "customerLabel": "Encontrar servicios", + "customerDescription": "Tu cuenta siempre está lista para encontrar y contactar profesionales.", + "providerLabel": "Ofrecer servicios", + "providerDescription": "Activa esta opción para preparar tu perfil profesional en los próximos pasos.", + "enabled": "Activo", + "disabled": "Inactivo", + "loading": "Cargando las capacidades de la cuenta…", + "saving": "Guardando…", + "loadError": "No hemos podido cargar las capacidades de tu cuenta.", + "retry": "Intentar de nuevo" + } }, "Errors": { "title": "Algo salió mal", diff --git a/frontend/messages/pt-PT.json b/frontend/messages/pt-PT.json index 3772bc5..f79ae0c 100644 --- a/frontend/messages/pt-PT.json +++ b/frontend/messages/pt-PT.json @@ -25,7 +25,21 @@ }, "Account": { "title": "Conta Juntly", - "description": "A sua sessão está ativa. Os perfis e funcionalidades do marketplace serão disponibilizados aqui à medida que forem implementados." + "description": "A sua sessão está ativa. Configure como pretende utilizar a Juntly.", + "capabilities": { + "title": "Como utiliza a Juntly", + "description": "A sua conta pode encontrar serviços e, se quiser, também disponibilizá-los.", + "customerLabel": "Encontrar serviços", + "customerDescription": "A sua conta está sempre pronta para procurar e contactar prestadores.", + "providerLabel": "Disponibilizar serviços", + "providerDescription": "Ative esta opção para preparar o seu perfil de prestador nos próximos passos.", + "enabled": "Ativo", + "disabled": "Inativo", + "loading": "A carregar as capacidades da conta…", + "saving": "A guardar…", + "loadError": "Não foi possível carregar as capacidades da conta.", + "retry": "Tentar novamente" + } }, "Errors": { "title": "Algo não correu bem", diff --git a/frontend/src/app/[locale]/account/page.test.tsx b/frontend/src/app/[locale]/account/page.test.tsx index 8dfebd9..3aebf3e 100644 --- a/frontend/src/app/[locale]/account/page.test.tsx +++ b/frontend/src/app/[locale]/account/page.test.tsx @@ -12,6 +12,11 @@ vi.mock("next-intl/server", () => ({ vi.mock("@/features/auth/require-session", () => ({ requireAuthenticatedUser: mocks.requireAuthenticatedUser, })); +vi.mock("@/features/account/account-capabilities-card", () => ({ + AccountCapabilitiesCard: ({ copy }: { copy: { providerLabel: string } }) => ( +
{copy.providerLabel}
+ ), +})); import AccountPage, { dynamic } from "./page"; @@ -30,6 +35,22 @@ describe("AccountPage", () => { mocks.getTranslations.mockResolvedValue( (key: string) => ({ + "capabilities.customerDescription": + "A sua conta pode sempre procurar prestadores.", + "capabilities.customerLabel": "Encontrar serviços", + "capabilities.description": + "Escolha se também pretende disponibilizar serviços.", + "capabilities.disabled": "Inativo", + "capabilities.enabled": "Ativo", + "capabilities.loadError": + "Não foi possível carregar as capacidades da conta.", + "capabilities.loading": "A carregar as capacidades da conta…", + "capabilities.providerDescription": + "Ative esta opção para preparar o seu perfil de prestador.", + "capabilities.providerLabel": "Disponibilizar serviços", + "capabilities.retry": "Tentar novamente", + "capabilities.saving": "A guardar…", + "capabilities.title": "Como utiliza a Juntly", description: "A sua sessão está ativa.", title: "Conta Juntly", })[key], @@ -46,5 +67,8 @@ describe("AccountPage", () => { screen.getByRole("heading", { name: "Conta Juntly" }), ).toBeInTheDocument(); expect(screen.getByText("A sua sessão está ativa.")).toBeInTheDocument(); + expect(screen.getByTestId("account-capabilities-card")).toHaveTextContent( + "Disponibilizar serviços", + ); }); }); diff --git a/frontend/src/app/[locale]/account/page.tsx b/frontend/src/app/[locale]/account/page.tsx index 05eae94..40315d8 100644 --- a/frontend/src/app/[locale]/account/page.tsx +++ b/frontend/src/app/[locale]/account/page.tsx @@ -2,6 +2,7 @@ import { getTranslations } from "next-intl/server"; import { hasLocale } from "next-intl"; import { notFound } from "next/navigation"; +import { AccountCapabilitiesCard } from "@/features/account/account-capabilities-card"; import { requireAuthenticatedUser } from "@/features/auth/require-session"; import { routing } from "@/i18n/routing"; @@ -20,10 +21,24 @@ export default async function AccountPage({ params }: AccountPageProps) { await requireAuthenticatedUser(locale); const t = await getTranslations("Account"); + const capabilityCopy = { + title: t("capabilities.title"), + description: t("capabilities.description"), + customerLabel: t("capabilities.customerLabel"), + customerDescription: t("capabilities.customerDescription"), + providerLabel: t("capabilities.providerLabel"), + providerDescription: t("capabilities.providerDescription"), + enabled: t("capabilities.enabled"), + disabled: t("capabilities.disabled"), + loading: t("capabilities.loading"), + saving: t("capabilities.saving"), + loadError: t("capabilities.loadError"), + retry: t("capabilities.retry"), + }; return ( -
-
+
+

Juntly

@@ -31,6 +46,7 @@ export default async function AccountPage({ params }: AccountPageProps) { {t("title")}

{t("description")}

+
); diff --git a/frontend/src/features/account/account-capabilities-card.test.tsx b/frontend/src/features/account/account-capabilities-card.test.tsx new file mode 100644 index 0000000..9deb701 --- /dev/null +++ b/frontend/src/features/account/account-capabilities-card.test.tsx @@ -0,0 +1,134 @@ +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { AccountCapabilitiesCard } from "./account-capabilities-card"; + +const copy = { + title: "Como utiliza a Juntly", + description: "Escolha se também pretende disponibilizar serviços.", + customerLabel: "Encontrar serviços", + customerDescription: "A sua conta pode sempre procurar prestadores.", + providerLabel: "Disponibilizar serviços", + providerDescription: + "Ative esta opção para preparar o seu perfil de prestador.", + enabled: "Ativo", + disabled: "Inativo", + loading: "A carregar as capacidades da conta…", + saving: "A guardar…", + loadError: "Não foi possível carregar as capacidades da conta.", + retry: "Tentar novamente", +}; + +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe("AccountCapabilitiesCard", () => { + it("shows loading then the implicit customer and current provider capabilities", async () => { + const response = deferred(); + vi.stubGlobal( + "fetch", + vi.fn(() => response.promise), + ); + + render(); + + expect(screen.getByText(copy.loading)).toBeInTheDocument(); + response.resolve(accountResponse(false)); + + expect(await screen.findByText(copy.customerLabel)).toBeInTheDocument(); + expect(screen.getByText(copy.enabled)).toBeInTheDocument(); + expect( + screen.getByRole("switch", { name: copy.providerLabel }), + ).toHaveAttribute("aria-checked", "false"); + expect(document.body.textContent).not.toMatch( + /[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i, + ); + }); + + it("updates provider capability through the same-origin BFF and locks controls while saving", async () => { + const update = deferred(); + const fetchMock = vi + .fn() + .mockResolvedValueOnce(accountResponse(false)) + .mockImplementationOnce(() => update.promise); + vi.stubGlobal("fetch", fetchMock); + + render(); + const toggle = await screen.findByRole("switch", { + name: copy.providerLabel, + }); + + fireEvent.click(toggle); + + expect(toggle).toBeDisabled(); + expect(screen.getByText(copy.saving)).toBeInTheDocument(); + expect(fetchMock).toHaveBeenLastCalledWith( + "/api/v1/me/account", + expect.objectContaining({ + method: "PUT", + body: JSON.stringify({ providerEnabled: true }), + }), + ); + + update.resolve(accountResponse(true)); + await waitFor(() => expect(toggle).toHaveAttribute("aria-checked", "true")); + expect(toggle).not.toBeDisabled(); + }); + + it("shows a controlled error and retries without exposing upstream details", async () => { + const fetchMock = vi + .fn() + .mockRejectedValueOnce(new Error("ECONNREFUSED http://internal-api")) + .mockResolvedValueOnce(accountResponse(false)); + vi.stubGlobal("fetch", fetchMock); + + render(); + + expect(await screen.findByText(copy.loadError)).toBeInTheDocument(); + expect(document.body.textContent).not.toContain("internal-api"); + expect(document.body.textContent).not.toContain("ECONNREFUSED"); + + fireEvent.click(screen.getByRole("button", { name: copy.retry })); + + expect(await screen.findByText(copy.customerLabel)).toBeInTheDocument(); + expect(fetchMock).toHaveBeenCalledTimes(2); + }); + + it("rejects malformed or expanded account responses", async () => { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue( + Response.json({ + customerEnabled: true, + providerEnabled: false, + onboardingCompletedAt: "2026-08-23T12:05:00Z", + internalUserId: "not-public", + }), + ), + ); + + render(); + + expect(await screen.findByText(copy.loadError)).toBeInTheDocument(); + expect(document.body.textContent).not.toContain("not-public"); + }); +}); + +function accountResponse(providerEnabled: boolean): Response { + return Response.json({ + customerEnabled: true, + providerEnabled, + onboardingCompletedAt: "2026-08-23T12:05:00.123456Z", + }); +} + +function deferred() { + let resolve!: (value: T) => void; + let reject!: (reason?: unknown) => void; + const promise = new Promise((resolvePromise, rejectPromise) => { + resolve = resolvePromise; + reject = rejectPromise; + }); + return { promise, reject, resolve }; +} diff --git a/frontend/src/features/account/account-capabilities-card.tsx b/frontend/src/features/account/account-capabilities-card.tsx new file mode 100644 index 0000000..44ceb74 --- /dev/null +++ b/frontend/src/features/account/account-capabilities-card.tsx @@ -0,0 +1,265 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; + +type AccountCapabilities = { + customerEnabled: true; + providerEnabled: boolean; + onboardingCompletedAt: string; +}; + +export type AccountCapabilitiesCopy = { + title: string; + description: string; + customerLabel: string; + customerDescription: string; + providerLabel: string; + providerDescription: string; + enabled: string; + disabled: string; + loading: string; + saving: string; + loadError: string; + retry: string; +}; + +type AccountCapabilitiesCardProps = { + copy: AccountCapabilitiesCopy; +}; + +export function AccountCapabilitiesCard({ + copy, +}: AccountCapabilitiesCardProps) { + const [account, setAccount] = useState(null); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [failed, setFailed] = useState(false); + const requestGeneration = useRef(0); + + async function loadAccount() { + const generation = ++requestGeneration.current; + setLoading(true); + setFailed(false); + + try { + const nextAccount = await fetchAccount(); + if (generation === requestGeneration.current) { + setAccount(nextAccount); + } + } catch { + if (generation === requestGeneration.current) { + setFailed(true); + } + } finally { + if (generation === requestGeneration.current) { + setLoading(false); + } + } + } + + useEffect(() => { + const generation = ++requestGeneration.current; + + async function loadInitialAccount() { + try { + const nextAccount = await fetchAccount(); + if (generation === requestGeneration.current) { + setAccount(nextAccount); + } + } catch { + if (generation === requestGeneration.current) { + setFailed(true); + } + } finally { + if (generation === requestGeneration.current) { + setLoading(false); + } + } + } + + void loadInitialAccount(); + return () => { + requestGeneration.current += 1; + }; + }, []); + + async function updateProviderCapability() { + if (!account || saving) { + return; + } + + const generation = ++requestGeneration.current; + const providerEnabled = !account.providerEnabled; + setSaving(true); + setFailed(false); + + try { + const response = await fetch("/api/v1/me/account", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ providerEnabled }), + }); + const nextAccount = await parseAccountResponse(response); + if (generation === requestGeneration.current) { + setAccount(nextAccount); + } + } catch { + if (generation === requestGeneration.current) { + setFailed(true); + } + } finally { + if (generation === requestGeneration.current) { + setSaving(false); + } + } + } + + if (loading && !account) { + return ( +
+ {copy.loading} +
+ ); + } + + if (!account) { + return ( +
+

{copy.loadError}

+ +
+ ); + } + + return ( +
+

+ {copy.title} +

+

{copy.description}

+ + {failed ? ( +

+ {copy.loadError} +

+ ) : null} + +
+
+
+
+

{copy.customerLabel}

+

+ {copy.customerDescription} +

+
+ + {copy.enabled} + +
+
+ +
+
+
+

+ {copy.providerLabel} +

+

+ {copy.providerDescription} +

+

+ {saving + ? copy.saving + : account.providerEnabled + ? copy.enabled + : copy.disabled} +

+
+ +
+
+
+
+ ); +} + +async function parseAccountResponse( + response: Response, +): Promise { + if (!response.ok) { + throw new Error("account unavailable"); + } + const value: unknown = await response.json(); + if (!isAccountCapabilities(value)) { + throw new Error("invalid account response"); + } + return value; +} + +async function fetchAccount(): Promise { + const response = await fetch("/api/v1/me/account", { method: "GET" }); + return parseAccountResponse(response); +} + +function isAccountCapabilities(value: unknown): value is AccountCapabilities { + if (value === null || typeof value !== "object" || Array.isArray(value)) { + return false; + } + const keys = Object.keys(value).sort(); + const expected = [ + "customerEnabled", + "onboardingCompletedAt", + "providerEnabled", + ]; + const candidate = value as Record; + return ( + keys.length === expected.length && + keys.every((key, index) => key === expected[index]) && + candidate.customerEnabled === true && + typeof candidate.providerEnabled === "boolean" && + typeof candidate.onboardingCompletedAt === "string" && + !Number.isNaN(Date.parse(candidate.onboardingCompletedAt)) + ); +} diff --git a/frontend/src/i18n/messages.test.ts b/frontend/src/i18n/messages.test.ts index 47054a6..fdd2048 100644 --- a/frontend/src/i18n/messages.test.ts +++ b/frontend/src/i18n/messages.test.ts @@ -55,4 +55,28 @@ describe("locale messages", () => { } } }); + + it("defines the complete account capability copy in every locale", () => { + const capabilityKeys = [ + "Account.capabilities.customerDescription", + "Account.capabilities.customerLabel", + "Account.capabilities.description", + "Account.capabilities.disabled", + "Account.capabilities.enabled", + "Account.capabilities.loadError", + "Account.capabilities.loading", + "Account.capabilities.providerDescription", + "Account.capabilities.providerLabel", + "Account.capabilities.retry", + "Account.capabilities.saving", + "Account.capabilities.title", + ]; + + for (const messages of Object.values(locales)) { + for (const key of capabilityKeys) { + expect(messages[key]).toBeTypeOf("string"); + expect(messages[key].trim()).not.toBe(""); + } + } + }); }); From 35080097562cbb08abe92fa33651f2e357ed9b90 Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 15:48:26 +0100 Subject: [PATCH 11/64] fix: bound local Clerk clock skew tolerance --- backend/.env.example | 2 + backend/cmd/api/config.go | 14 ++++++ backend/cmd/api/config_test.go | 50 +++++++++++++++++++ backend/internal/authn/clerk_verifier.go | 20 ++++++-- backend/internal/authn/clerk_verifier_test.go | 36 +++++++++++++ compose.yaml | 1 + frontend/src/proxy.test.ts | 31 +++++++++++- frontend/src/proxy.ts | 6 ++- 8 files changed, 153 insertions(+), 7 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 178718a..70c2e9d 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -5,3 +5,5 @@ CLERK_SECRET_KEY= CLERK_JWT_KEY= # Comma-separated exact allowed Clerk authorized-party origins. CLERK_AUTHORIZED_PARTIES=http://localhost:4200 +# Optional Go duration, capped at 30s. Leave empty unless a local clock cannot synchronize. +CLERK_CLOCK_SKEW= diff --git a/backend/cmd/api/config.go b/backend/cmd/api/config.go index c0c39b5..bfa254b 100644 --- a/backend/cmd/api/config.go +++ b/backend/cmd/api/config.go @@ -3,6 +3,7 @@ package main import ( "errors" "strings" + "time" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/authn" ) @@ -19,11 +20,16 @@ func loadRuntimeConfig(lookup func(string) string) (runtimeConfig, error) { if databaseURL == "" { return runtimeConfig{}, ErrInvalidRuntimeConfig } + clockSkew, err := parseOptionalDuration(lookup("CLERK_CLOCK_SKEW")) + if err != nil { + return runtimeConfig{}, ErrInvalidRuntimeConfig + } verifier, err := authn.NewClerkVerifier(authn.ClerkVerifierConfig{ SecretKey: lookup("CLERK_SECRET_KEY"), JWTKey: lookup("CLERK_JWT_KEY"), AuthorizedParties: strings.Split(lookup("CLERK_AUTHORIZED_PARTIES"), ","), + ClockSkew: clockSkew, }) if err != nil { return runtimeConfig{}, ErrInvalidRuntimeConfig @@ -31,3 +37,11 @@ func loadRuntimeConfig(lookup func(string) string) (runtimeConfig, error) { return runtimeConfig{databaseURL: databaseURL, verifier: verifier}, nil } + +func parseOptionalDuration(value string) (time.Duration, error) { + value = strings.TrimSpace(value) + if value == "" { + return 0, nil + } + return time.ParseDuration(value) +} diff --git a/backend/cmd/api/config_test.go b/backend/cmd/api/config_test.go index 255f57e..d2861b2 100644 --- a/backend/cmd/api/config_test.go +++ b/backend/cmd/api/config_test.go @@ -64,3 +64,53 @@ func TestLoadRuntimeConfigBuildsVerifierWithoutPersistingCredentials(t *testing. t.Fatal("verifier is nil") } } + +func TestLoadRuntimeConfigAcceptsBoundedClerkClockSkew(t *testing.T) { + t.Parallel() + + _, err := loadRuntimeConfig(func(key string) string { + switch key { + case "DATABASE_URL": + return "postgresql://synthetic" + case "CLERK_SECRET_KEY": + return "synthetic-secret" + case "CLERK_AUTHORIZED_PARTIES": + return "http://localhost:4200" + case "CLERK_CLOCK_SKEW": + return "30s" + default: + return "" + } + }) + if err != nil { + t.Fatalf("load runtime config: %v", err) + } +} + +func TestLoadRuntimeConfigRejectsInvalidClerkClockSkew(t *testing.T) { + t.Parallel() + + for _, value := range []string{"not-a-duration", "-1s", "30.000000001s"} { + value := value + t.Run(value, func(t *testing.T) { + t.Parallel() + _, err := loadRuntimeConfig(func(key string) string { + switch key { + case "DATABASE_URL": + return "postgresql://synthetic" + case "CLERK_SECRET_KEY": + return "synthetic-secret" + case "CLERK_AUTHORIZED_PARTIES": + return "http://localhost:4200" + case "CLERK_CLOCK_SKEW": + return value + default: + return "" + } + }) + if err == nil { + t.Fatal("error = nil, want invalid clock-skew rejection") + } + }) + } +} diff --git a/backend/internal/authn/clerk_verifier.go b/backend/internal/authn/clerk_verifier.go index b129cc8..d07d078 100644 --- a/backend/internal/authn/clerk_verifier.go +++ b/backend/internal/authn/clerk_verifier.go @@ -4,12 +4,13 @@ import ( "context" "errors" "strings" + "time" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" clerk "github.com/clerk/clerk-sdk-go/v2" clerkhttp "github.com/clerk/clerk-sdk-go/v2/http" "github.com/clerk/clerk-sdk-go/v2/jwks" clerkjwt "github.com/clerk/clerk-sdk-go/v2/jwt" - "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" ) var ( @@ -21,12 +22,14 @@ type ClerkVerifierConfig struct { SecretKey string JWTKey string AuthorizedParties []string + ClockSkew time.Duration } type clerkVerifier struct { jwk *clerk.JSONWebKey jwksClient *jwks.Client authorizedParties map[string]struct{} + clockSkew time.Duration } func NewClerkVerifier(config ClerkVerifierConfig) (Verifier, error) { @@ -34,8 +37,14 @@ func NewClerkVerifier(config ClerkVerifierConfig) (Verifier, error) { if len(parties) == 0 { return nil, ErrInvalidClerkVerifierConfig } + if config.ClockSkew < 0 || config.ClockSkew > 30*time.Second { + return nil, ErrInvalidClerkVerifierConfig + } - verifier := &clerkVerifier{authorizedParties: parties} + verifier := &clerkVerifier{ + authorizedParties: parties, + clockSkew: config.ClockSkew, + } if jwtKey := strings.TrimSpace(config.JWTKey); jwtKey != "" { params := &clerkhttp.AuthorizationParams{} if err := clerkhttp.JSONWebKey(jwtKey)(params); err != nil { @@ -57,10 +66,11 @@ func NewClerkVerifier(config ClerkVerifierConfig) (Verifier, error) { func (v *clerkVerifier) Verify(ctx context.Context, token string) (users.VerifiedIdentity, error) { claims, err := clerkjwt.Verify(ctx, &clerkjwt.VerifyParams{ - Token: token, - JWK: v.jwk, - JWKSClient: v.jwksClient, + Token: token, + JWK: v.jwk, + JWKSClient: v.jwksClient, AuthorizedPartyHandler: v.authorizedParty, + Leeway: v.clockSkew, }) if err != nil || claims == nil || claims.SessionID == "" || strings.TrimSpace(claims.Subject) == "" { return users.VerifiedIdentity{}, ErrUnverifiedClerkToken diff --git a/backend/internal/authn/clerk_verifier_test.go b/backend/internal/authn/clerk_verifier_test.go index c2cf0bb..c3098bf 100644 --- a/backend/internal/authn/clerk_verifier_test.go +++ b/backend/internal/authn/clerk_verifier_test.go @@ -8,6 +8,7 @@ import ( "encoding/pem" "errors" "testing" + "time" ) func TestNewClerkVerifierRejectsMissingVerificationMaterial(t *testing.T) { @@ -66,3 +67,38 @@ func testPublicKey(t *testing.T) string { } return string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der})) } + +func TestNewClerkVerifierAcceptsBoundedClockSkew(t *testing.T) { + t.Parallel() + + verifier, err := NewClerkVerifier(ClerkVerifierConfig{ + JWTKey: testPublicKey(t), + AuthorizedParties: []string{"http://localhost:4200"}, + ClockSkew: 30 * time.Second, + }) + if err != nil { + t.Fatalf("new Clerk verifier: %v", err) + } + concrete, ok := verifier.(*clerkVerifier) + if !ok { + t.Fatalf("verifier type = %T, want *clerkVerifier", verifier) + } + if concrete.clockSkew != 30*time.Second { + t.Fatalf("clock skew = %s, want 30s", concrete.clockSkew) + } +} + +func TestNewClerkVerifierRejectsUnsafeClockSkew(t *testing.T) { + t.Parallel() + + for _, clockSkew := range []time.Duration{-time.Second, 30*time.Second + time.Nanosecond} { + _, err := NewClerkVerifier(ClerkVerifierConfig{ + JWTKey: testPublicKey(t), + AuthorizedParties: []string{"http://localhost:4200"}, + ClockSkew: clockSkew, + }) + if !errors.Is(err, ErrInvalidClerkVerifierConfig) { + t.Fatalf("clock skew %s error = %v, want ErrInvalidClerkVerifierConfig", clockSkew, err) + } + } +} diff --git a/compose.yaml b/compose.yaml index 5e0a9ea..d1d5b7a 100644 --- a/compose.yaml +++ b/compose.yaml @@ -8,6 +8,7 @@ services: CLERK_SECRET_KEY: ${CLERK_SECRET_KEY:-} CLERK_JWT_KEY: ${CLERK_JWT_KEY:-} CLERK_AUTHORIZED_PARTIES: ${CLERK_AUTHORIZED_PARTIES:-http://localhost:4200} + CLERK_CLOCK_SKEW: ${CLERK_CLOCK_SKEW:-} healthcheck: test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8080/api/v1/health || exit 1"] interval: 5s diff --git a/frontend/src/proxy.test.ts b/frontend/src/proxy.test.ts index f53d698..6b49101 100644 --- a/frontend/src/proxy.test.ts +++ b/frontend/src/proxy.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => { const apiResponse = { source: "api" }; @@ -36,6 +36,14 @@ async function loadProxy() { } describe("proxy", () => { + beforeEach(() => { + mocks.clerkMiddleware.mockClear(); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + }); + it("composes Clerk with locale routing and matches API routes", async () => { const { config } = await loadProxy(); @@ -56,4 +64,25 @@ describe("proxy", () => { clerkProxy({}, { nextUrl: { pathname: "/pt-PT" } }), ).resolves.toBe(mocks.intlResponse); }); + + it("uses bounded clock tolerance only during local development", async () => { + vi.stubEnv("NODE_ENV", "development"); + + await loadProxy(); + + expect(mocks.clerkMiddleware).toHaveBeenCalledWith(expect.any(Function), { + clockSkewInMs: 30_000, + }); + }); + + it("keeps production token verification at Clerk's strict default", async () => { + vi.stubEnv("NODE_ENV", "production"); + + await loadProxy(); + + expect(mocks.clerkMiddleware).toHaveBeenCalledWith( + expect.any(Function), + undefined, + ); + }); }); diff --git a/frontend/src/proxy.ts b/frontend/src/proxy.ts index 49515da..ffc8f98 100644 --- a/frontend/src/proxy.ts +++ b/frontend/src/proxy.ts @@ -5,6 +5,10 @@ import createMiddleware from "next-intl/middleware"; import { routing } from "./i18n/routing"; const intlMiddleware = createMiddleware(routing); +const clerkOptions = + process.env.NODE_ENV === "development" + ? { clockSkewInMs: 30_000 } + : undefined; export default clerkMiddleware(async (_auth, request) => { if ( @@ -16,7 +20,7 @@ export default clerkMiddleware(async (_auth, request) => { } return intlMiddleware(request); -}); +}, clerkOptions); export const config = { matcher: [ From 8743316c4bddc982ab8eab99b76724fcb81b75f7 Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 16:06:56 +0100 Subject: [PATCH 12/64] docs: define taxonomy and provider profile slice --- ...nomy-locations-provider-profiles-design.md | 353 ++++++++++++++++++ 1 file changed, 353 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-23-taxonomy-locations-provider-profiles-design.md diff --git a/docs/superpowers/specs/2026-08-23-taxonomy-locations-provider-profiles-design.md b/docs/superpowers/specs/2026-08-23-taxonomy-locations-provider-profiles-design.md new file mode 100644 index 0000000..cbc8529 --- /dev/null +++ b/docs/superpowers/specs/2026-08-23-taxonomy-locations-provider-profiles-design.md @@ -0,0 +1,353 @@ +# Taxonomy, Locations, and Provider Profiles Design + +**Status:** Approved by SourceSensei on 2026-08-23 + +## Goal + +Deliver Juntly Slice 2 as a privacy-safe supply foundation: database-configurable service taxonomy, a source-verifiable Portugal launch-area location hierarchy with PostGIS radius behavior, and owner-only provider profiles managed through the existing authenticated BFF → Go → PostgreSQL authority chain. + +## Scope decisions + +1. Taxonomy is database-configurable and product-readable in Slice 2. Taxonomy administration APIs and UI are deferred to Slice 11, where platform-admin authority is introduced. +2. Location reference data includes a source-verified launch-area subset rather than a schema-only delivery or full Portugal import. +3. Provider profiles remain owner-only until Slice 4 defines and tests a separate public projection. +4. No temporary Clerk metadata, operator secret, paid entitlement, or provider capability is treated as platform-admin authority. + +## Authority and request path + +```text +browser +→ localized Next.js UI +→ same-origin Next.js BFF +→ generated OpenAPI TypeScript client +→ Go REST transport +→ taxonomy / locations / providers application services +→ Ent and reviewed parameterized PostGIS repositories +→ project-owned Supabase PostgreSQL/PostGIS +``` + +Clerk remains identity authority. Go resolves the verified Clerk subject to the opaque internal user, checks `providerEnabled`, enforces ownership, validates every referenced category/location/language, and owns transactions. Browser state and hidden controls are presentation only. + +## Taxonomy model + +### `service_categories` + +- `id uuid primary key` +- `parent_id uuid null references service_categories(id)` +- `slug text not null unique` +- `active boolean not null default true` +- `sort_order integer not null` +- `created_at timestamptz not null` +- `updated_at timestamptz not null` + +Rules: + +- Only two levels are permitted: category and subcategory. +- A category cannot parent itself. +- Inactive records remain durable but are omitted from new provider/listing choices. +- Slugs are stable lookup identities and are never translated. + +### `service_category_translations` + +- Composite primary key: `(category_id, locale)` +- `category_id uuid references service_categories(id) on delete cascade` +- `locale text` restricted initially to `pt-PT`, `en`, and `es` +- `name text not null` +- `description text null` + +Every seeded active category has all three locale rows. API output uses the requested supported locale and fails closed rather than mixing locales silently. + +### Initial category seed + +The initial database seed uses product-approved service concepts, not component constants: + +- `home-repairs`: plumbing, electrical work, construction, small repairs +- `home-and-garden`: cleaning, gardening +- `rural-and-transport`: agricultural assistance, transport +- `care-and-learning`: elderly assistance, animal care, private lessons +- `food-and-technology`: meal preparation, computer repair + +Seed IDs and ordering are deterministic. Later changes are forward migrations until Slice 11 provides server-authorized administration. + +## Language reference model + +### `spoken_languages` + +- `code text primary key` using stable BCP 47 codes +- `active boolean not null default true` +- `sort_order integer not null` + +### `spoken_language_translations` + +- Composite primary key: `(language_code, locale)` +- Localized display name in `pt-PT`, `en`, and `es` + +The initial active values are `pt-PT`, `en`, and `es`. Provider code validates against active database records; UI code does not own the vocabulary. + +## Location model + +### Administrative hierarchy + +`administrative_areas` stores: + +- opaque UUID +- `source` (`caop` for the initial hierarchy) +- `source_version` (`2025`) +- stable external administrative code +- `kind`: country, district, municipality, or parish +- official name +- nullable parent UUID +- active flag +- timestamps + +The unique identity is `(source, external_code)`. Parent-kind transitions are closed: + +```text +country → district → municipality → parish +``` + +DGT describes CAOP as Portugal's official administrative-boundary record, maintains it, publishes CAOP 2025 as the current version, provides a GeoPackage download, and publishes a PostgreSQL/PostGIS-adapted conceptual model.[1] INE independently lists the relevant Idanha-a-Nova municipal/parish entities, including Penha Garcia, Monsanto and Idanha-a-Velha, and Zebreira and Segura.[2] + +### Search localities + +`localities` stores: + +- opaque UUID +- stable slug +- display name +- parent parish UUID +- PostGIS `geography(Point, 4326)` reference center +- source name +- source element identifier +- source version/retrieval date +- active flag +- timestamps + +Initial localities are: + +- Castelo Branco +- Idanha-a-Nova +- Zebreira +- Penha Garcia +- Monsanto + +The containing administrative hierarchy comes from CAOP 2025. Locality center points are a reviewed one-time OpenStreetMap extraction because villages/towns are place features rather than CAOP administrative boundaries. The import tool uses the public Nominatim service only during development, serially at no more than one request per second, with an identifying User-Agent, and caches the reviewed result; it is never a product runtime dependency.[3] Committed provenance retains OSM type/element ID and the UI/reference API includes OpenStreetMap attribution because OSM data is ODbL-licensed and requires credit.[4] + +No downloaded national archive, Nominatim response dump, or generated temporary database is committed. The reviewed launch seed manifest is small, deterministic, source-attributed product reference data—not mock marketplace data. + +### Radius query + +The locations repository uses a reviewed parameterized SQL query: + +```text +ST_DWithin(candidate.center, origin.center, radius_meters) +ORDER BY ST_Distance(candidate.center, origin.center), candidate.id +``` + +Rules: + +- Radius is an integer from 1 to 200 kilometres at public/application boundaries. +- Only active localities participate. +- Origin must be an active locality. +- The UUID tie-breaker makes equal-distance ordering deterministic. +- Real PostgreSQL tests include at least three localities, multiple in-range results, one out-of-range result, boundary behavior, and deterministic ordering. + +## Provider profile model + +### `provider_profiles` + +- `internal_user_id uuid primary key references user_accounts(internal_user_id) on delete cascade` +- `display_name text not null`, trimmed length 2–100 +- `provider_type text not null`: individual, professional, or business +- `bio text not null`, trimmed maximum 1,000 characters +- `primary_locality_id uuid references localities(id)` +- `max_travel_distance_km integer not null`, range 0–200 +- `travels_to_customer boolean not null` +- `receives_customer boolean not null` +- `remote_services boolean not null` +- `created_at timestamptz not null` +- `updated_at timestamptz not null` + +At least one service mode is true. A zero travel radius is allowed only when a non-travel mode remains enabled. + +### `provider_service_localities` + +- Composite primary key: `(internal_user_id, locality_id)` +- Profile FK and locality FK with cascade/restrict behavior appropriate to ownership/reference data +- The list contains 1–20 unique active localities and must contain the primary locality. + +### `provider_spoken_languages` + +- Composite primary key: `(internal_user_id, language_code)` +- The list contains 1–10 unique active language codes. + +### Privacy boundary + +Slice 2 does not collect or return: + +- email, phone, WhatsApp, or preferred contact method +- exact address or provider-specific coordinates +- identity/business documents +- verification claims +- payment/subscription data +- listings, portfolio media, reviews, response statistics, or availability +- Clerk subject, token/session values, or internal-user UUID + +A provider profile has no public route or publication state in Slice 2. Slice 4 must create an explicit allowlisted public DTO and SEO/privacy tests before any provider data becomes public. + +## Provider capability and ownership + +- A verified internal user must have `providerEnabled=true` before profile GET/PUT is available. +- A provider-disabled account receives `403 FORBIDDEN` and no profile row is created. +- The caller cannot submit an owner/internal-user ID. +- GET and PUT always derive ownership from verified identity. +- Cross-user lookup and mutation are absent from the public contract. +- Concurrent first PUT operations resolve through the unique owner key and one transactional winner. + +## Application service and transaction + +The provider service consumes: + +```go +type AccountAuthorizer interface { + RequireProvider(context.Context, users.VerifiedIdentity) (users.InternalUser, error) +} +``` + +The service validates the full replacement request before opening a transaction. One transaction: + +1. loads or creates the owner profile; +2. replaces scalar profile fields; +3. replaces service-locality links; +4. replaces spoken-language links; +5. rereads and returns the canonical owner-only profile. + +Any invalid reference or failed child replacement rolls back the complete mutation. A repeat with identical input is idempotent in durable state. No raw request body is logged or persisted. + +## API contract + +### Reference reads + +```text +GET /api/v1/catalog/categories?locale=pt-PT +GET /api/v1/reference/localities?locale=pt-PT +GET /api/v1/reference/localities?locale=pt-PT&nearLocalityId=&radiusKm=<1..200> +GET /api/v1/reference/languages?locale=pt-PT +``` + +These endpoints expose only active reference rows, stable public UUIDs/slugs, localized names, hierarchy labels, distance where requested, and required OSM attribution metadata. They expose no provider/user records. + +### Owner profile + +```text +GET /api/v1/me/provider-profile +PUT /api/v1/me/provider-profile +``` + +GET returns either a closed nullable profile envelope or the current complete owner-only profile. PUT is strict full replacement and accepts exactly: + +```json +{ + "displayName": "string", + "providerType": "individual | professional | business", + "bio": "string", + "primaryLocalityId": "uuid", + "serviceLocalityIds": ["uuid"], + "maxTravelDistanceKm": 25, + "travelsToCustomer": true, + "receivesCustomer": false, + "remoteServices": false, + "languageCodes": ["pt-PT"] +} +``` + +Unknown properties, omitted required fields, explicit nulls, duplicate IDs/codes, invalid bounds, inactive/missing references, primary locality omission, and invalid service-mode combinations return correlated `400 INVALID_REQUEST` without service invocation. Authentication failures return `401`, provider-disabled access returns `403`, and dependency failures return generic `503`. + +## Same-origin BFF + +The Next.js BFF mirrors the Go/OpenAPI boundaries: + +- obtains Clerk session/token server-side; +- never accepts browser ownership/subject/token fields; +- strictly validates PUT before generated-client invocation; +- forwards bearer and request ID only; +- validates exact upstream response keys and correlation parity; +- maps topology/upstream/malformed failures to generic `503`; +- preserves `401`, `403`, and local `400` allowlisted envelopes. + +Reference endpoints are same-origin and use generated clients but require no Clerk bearer. + +## Localized UI + +Protected route: + +```text +/:locale/account/provider-profile +``` + +The account capability page links to it only when provider capability is enabled. The page remains protected server-side and owner-only. + +The mobile-first form provides: + +- short profile fields; +- database-loaded provider type copy, languages, primary locality, and service localities; +- three accessible service-mode controls; +- bounded travel-radius input; +- loading, empty, save, success, validation, and safe retry states; +- keyboard/focus support and 44×44px minimum controls; +- pt-PT and English copy with Spanish structural parity; +- visible OpenStreetMap attribution near locality reference use. + +No browser component renders or stores internal owner IDs, precise addresses, contact details, or upstream diagnostics. + +## Source import and reproducibility + +A developer-only source tool: + +1. downloads CAOP 2025 GeoPackage to an OS temporary path; +2. verifies a pinned SHA-256 recorded in the source manifest; +3. extracts only required district/municipality/parish codes and parent relations; +4. queries the five locality centers serially under the Nominatim policy; +5. writes a deterministic reviewed manifest containing only approved names, codes, points, source IDs, versions, retrieval dates, and attribution; +6. deletes downloaded/temp artifacts after verification. + +The application and tests never call Nominatim or download CAOP at runtime. Updating source versions requires a reviewed forward migration and regenerated manifest. + +## Error and logging policy + +- Public errors contain only allowlisted code/message/request ID. +- Logs may contain route, status, duration, operation, and correlation ID. +- Logs must not include profile biography, display name, token, subject, internal user ID, exact input arrays, database URL, source response bodies, or raw coordinates tied to a user. +- Reference import failures report source stage and count only, never downloaded payload fragments. + +## TDD and acceptance evidence + +Every task follows RED → GREEN → REFACTOR. Slice completion requires: + +1. migration/schema contracts and complete forward migration chain; +2. real PostgreSQL/PostGIS hierarchy and radius tests with multiple localities; +3. provider-disabled and cross-owner negative tests; +4. transactional profile create/update/child-replacement/concurrency proof; +5. strict Go and BFF contract matrices for unknown/null/duplicate/bound violations; +6. OpenAPI regeneration and drift check; +7. localized component/page/i18n tests; +8. full Go test/race/vet/build and frontend verify gates; +9. Compose config and whitespace/secret/privacy scans; +10. live reference HTTP evidence; +11. real approved Clerk browser profile create → read → replace → read proof showing only status, correlation parity, response shape, service-area/language counts, capability state, and stable owner identity without emitting values; +12. frozen candidate review and local commit before the next slice. + +## Explicit deferrals + +- Taxonomy mutation APIs/UI and platform-admin role: Slice 11. +- Public provider DTO/pages and SEO: Slice 4. +- Listings/category ownership: Slice 3. +- Contact methods/hours and contact reveal: Slice 5. +- Verification, documents, moderation UI, analytics, subscriptions, promotions, and payments: later approved slices. + +## Sources + +[1] https://www.dgterritorio.gov.pt/atividades/cartografia/cartografia-tematica/caop — Carta Administrativa Oficial de Portugal — DGT +[2] https://www.ine.pt/ngt_server/attachfileu.jsp?look_parentBoui=456019385&att_display=n&att_download=y — 2025 Entidades do Setor Institucional das Administrações Públicas — INE +[3] https://operations.osmfoundation.org/policies/nominatim — Nominatim Usage Policy — OpenStreetMap Foundation +[4] https://www.openstreetmap.org/copyright — Copyright and License — OpenStreetMap From d72ba9dbc90a88cc7e9745f6ad4c81480a449ecb Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 16:21:59 +0100 Subject: [PATCH 13/64] docs: plan taxonomy and provider profiles --- ...23-taxonomy-locations-provider-profiles.md | 571 ++++++++++++++++++ 1 file changed, 571 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-23-taxonomy-locations-provider-profiles.md diff --git a/docs/superpowers/plans/2026-08-23-taxonomy-locations-provider-profiles.md b/docs/superpowers/plans/2026-08-23-taxonomy-locations-provider-profiles.md new file mode 100644 index 0000000..1fc5df7 --- /dev/null +++ b/docs/superpowers/plans/2026-08-23-taxonomy-locations-provider-profiles.md @@ -0,0 +1,571 @@ +# Taxonomy, Locations, and Provider Profiles Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Deliver database-configurable service taxonomy, source-verifiable Portugal launch locations with PostGIS radius queries, and owner-only provider profiles through Juntly's authenticated full-stack authority chain. + +**Architecture:** Reference data is seeded by forward migrations from a deterministic reviewed manifest. Ent schemas model taxonomy, locales, languages, localities, provider profiles, and composite edge schemas; the locality table also has a SQL-generated PostGIS geography point used by a parameterized repository. Go owns provider capability, ownership, validation, transactions, and strict OpenAPI transports; Next.js provides same-origin BFF routes and a localized owner-only onboarding form. + +**Tech Stack:** Python 3.12 stdlib source verifier, DGT CAOP 2025 GeoPackage, OpenStreetMap/Nominatim one-time source verification, Go 1.26, Ent 0.14.6, pgx/PostgreSQL/PostGIS, Supabase migrations, OpenAPI 3.1, generated TypeScript, Next.js 16.3.1, Clerk, Vitest, Testing Library. + +## Global Constraints + +- Taxonomy administration APIs/UI are deferred to Slice 11; Slice 2 taxonomy is database-configurable and read-only through product APIs. +- Provider profiles remain owner-only; no public provider DTO/page/metadata exists until Slice 4. +- A caller must have `providerEnabled=true`; customer capability, paid status, or Clerk metadata never grants provider/admin authority. +- Persist no email, phone, WhatsApp, exact provider address/coordinates, identity documents, verification claims, payment data, Clerk subject/token/session, or internal-user UUID in public/profile responses. +- Browser calls same-origin `/api/v1/...`; `JUNTLY_API_ORIGIN` stays server-only. +- All request/response objects are closed. Unknown properties, explicit nulls, duplicate IDs/codes, unsupported references, invalid bounds, and trailing JSON fail before service mutation. +- Migrations are forward-only and API startup never migrates schema. +- pt-PT is the default locale; English ships; Spanish has exact message-key parity. +- Real PostgreSQL/PostGIS tests use synthetic isolated profile rows and the canonical migration ledger; they must not skip during acceptance. +- Reference data may contain only reviewed public administrative/place facts and attribution. Downloaded CAOP/Nominatim response artifacts remain temporary and are deleted. +- Each behavioral unit follows observed RED → GREEN → REFACTOR, focused/full gates, a reviewed staged allowlist, and a local commit. No push or remote mutation. + +--- + +## Frozen launch reference values + +### CAOP source + +```text +URL: https://geo2.dgterritorio.gov.pt/caop/CAOP_Continente_2025-gpkg.zip +SHA-256: 87cd67f4b1fbadf23d9324e6fb231ff05531e4db347af36ccc7c6cbabe3ecd1d +Bytes: 111647845 +Layers: cont_distritos, cont_municipios, cont_freguesias +``` + +Administrative rows: + +```text +country: PT — Portugal +district: 05 — Castelo Branco +municipality: 0502 — Castelo Branco +municipality: 0505 — Idanha-a-Nova +parish: 050205 — Castelo Branco +parish: 050510 — Penha Garcia +parish: 050518 — União das freguesias de Idanha-a-Nova e Alcafozes +parish: 050520 — União das freguesias de Monsanto e Idanha-a-Velha +parish: 050521 — União das freguesias de Zebreira e Segura +``` + +### OSM locality centers + +```text +castelo-branco | relation 5396187 | 39.8266322, -7.4919318 | parent 050205 +idanha-a-nova | relation 5395738 | 39.9260883, -7.2436356 | parent 050518 +zebreira | node 440173641 | 39.8455920, -7.0703366 | parent 050521 +penha-garcia | relation 5431477 | 40.0422569, -7.0163521 | parent 050510 +monsanto | node 371426674 | 40.0387510, -7.1151133 | parent 050520 +``` + +The manifest records `source=OpenStreetMap`, source type/ID, retrieval date `2026-08-23`, ODbL URL, and attribution `© OpenStreetMap contributors`. + +### Initial taxonomy + +Top-level slugs and child slugs: + +```text +home-repairs: plumbing, electrical-work, construction, small-repairs +home-and-garden: cleaning, gardening +rural-and-transport: agricultural-assistance, transport +care-and-learning: elderly-assistance, animal-care, private-lessons +food-and-technology: meal-preparation, computer-repair +``` + +Supported locale/language codes are `pt-PT`, `en`, and `es`. + +## File map + +### Reference tooling/data + +- `reference/portugal/launch-area-2025.json` — deterministic reviewed source manifest. +- `scripts/build_launch_reference.py` — CAOP checksum/SQLite extraction plus policy-compliant Nominatim resolver. +- `scripts/tests/test_build_launch_reference.py` — offline synthetic archive/geocoder contract tests. + +### Persistence + +- `supabase/migrations/_create_taxonomy_locations_provider_profiles.sql` — reference/profile tables, PostGIS center, constraints, seeds. +- `backend/ent/schema/supportedlocale.go` +- `backend/ent/schema/servicecategory.go` +- `backend/ent/schema/servicecategorytranslation.go` — composite edge schema. +- `backend/ent/schema/spokenlanguage.go` +- `backend/ent/schema/spokenlanguagetranslation.go` — composite edge schema. +- `backend/ent/schema/administrativearea.go` +- `backend/ent/schema/locality.go` +- `backend/ent/schema/providerprofile.go` +- `backend/ent/schema/providerservicelocality.go` — composite edge schema. +- `backend/ent/schema/providerspokenlanguage.go` — composite edge schema. +- `backend/ent/schema/*_test.go` — structural privacy/constraint contracts. +- `backend/ent/**` — generated Ent output. +- `backend/internal/users/migration_contract_test.go` — complete SQL migration/seed contract. + +### Go modules + +- `backend/internal/reference/model.go` +- `backend/internal/reference/repository.go` +- `backend/internal/reference/sql_repository.go` +- `backend/internal/reference/service.go` +- `backend/internal/reference/*_test.go` +- `backend/internal/provideraccess/service.go` +- `backend/internal/provideraccess/service_test.go` +- `backend/internal/providers/model.go` +- `backend/internal/providers/repository.go` +- `backend/internal/providers/ent_repository.go` +- `backend/internal/providers/service.go` +- `backend/internal/providers/*_test.go` +- `backend/internal/httpapi/reference_handler.go` +- `backend/internal/httpapi/provider_profile_handler.go` +- related handler/router/OpenAPI tests +- `backend/cmd/api/main.go` — dependency composition using the existing DB/Ent client. + +### Contract/BFF/UI + +- `openapi/juntly-api.v1.yaml` +- `frontend/src/shared/api/generated/**` +- `frontend/src/app/api/v1/catalog/categories/route.ts` +- `frontend/src/app/api/v1/reference/localities/route.ts` +- `frontend/src/app/api/v1/reference/languages/route.ts` +- `frontend/src/app/api/v1/me/provider-profile/route.ts` +- matching route tests +- `frontend/src/features/provider/provider-profile-form.tsx` +- `frontend/src/features/provider/provider-profile-form.test.tsx` +- `frontend/src/app/[locale]/account/provider-profile/page.tsx` +- `frontend/src/app/[locale]/account/provider-profile/page.test.tsx` +- `frontend/src/features/account/account-capabilities-card.tsx` and test +- `frontend/messages/{pt-PT,en,es}.json` +- `frontend/src/i18n/messages.test.ts` + +## Task 1: Freeze and verify launch reference data + +**Files:** +- Create: `reference/portugal/launch-area-2025.json` +- Create: `scripts/build_launch_reference.py` +- Create: `scripts/tests/test_build_launch_reference.py` +- Modify: `.gitignore` only if a precise source-tool cache path needs exclusion; do not ignore broad data directories. + +**Interfaces:** + +```python +def verify_caop_archive(path: Path, expected_sha256: str) -> None: ... +def extract_administrative_rows(path: Path, required: tuple[RequiredArea, ...]) -> list[AdministrativeArea]: ... +def resolve_localities(resolve: Callable[[str], list[dict[str, object]]]) -> list[Locality]: ... +def build_manifest(caop_zip: Path, resolve: Callable[[str], list[dict[str, object]]]) -> dict[str, object]: ... +``` + +- [ ] **Step 1: Write offline RED tests.** + +Create a synthetic ZIP containing a SQLite GeoPackage-like database with `cont_distritos`, `cont_municipios`, and `cont_freguesias`. Assert checksum mismatch rejection, missing/duplicate exact row rejection, required code/name/parent extraction, deterministic ordering, exact five OSM element selection, request spacing hook invocation, and manifest output equality. Assert the manifest contains no postal code or source response dump. + +- [ ] **Step 2: Run RED.** + +```bash +python -m unittest scripts.tests.test_build_launch_reference -v +``` + +Expected: import failure because `scripts/build_launch_reference.py` is absent. + +- [ ] **Step 3: Implement the stdlib verifier.** + +Use `urllib.request`, `hashlib`, `zipfile`, `sqlite3`, `tempfile`, and `shutil`. The network CLI uses an identifying `User-Agent`, serial Nominatim requests with at least 1.05 seconds between calls, `limit=3`, `countrycodes=pt`, and exact allowlisted OSM type/ID selection from the frozen values. Always delete the temporary root in `finally`. + +- [ ] **Step 4: Write the deterministic manifest.** + +The checked-in JSON contains the frozen CAOP/OSM values above, source/license metadata, locale/category/language seeds, and no unreviewed candidates. + +- [ ] **Step 5: Run GREEN and reproducibility checks.** + +```bash +python -m unittest scripts.tests.test_build_launch_reference -v +python scripts/build_launch_reference.py --verify reference/portugal/launch-area-2025.json +``` + +The first is offline. The second is a deliberate one-time network verification; it compares bytes to the checked-in manifest and removes temporary files. + +- [ ] **Step 6: Commit.** + +```bash +git add reference/portugal/launch-area-2025.json scripts/build_launch_reference.py scripts/tests/test_build_launch_reference.py .gitignore +git commit -m "chore: verify launch reference data" +``` + +## Task 2: Add taxonomy, location, language, and provider-profile schema + +**Files:** +- Create the migration and ten Ent schema files listed in the file map. +- Create focused schema tests. +- Modify `backend/internal/users/migration_contract_test.go`. +- Regenerate `backend/ent/**` and `backend/go.sum` if the generator changes it. + +**Interfaces and schema rules:** + +- `SupportedLocale`: string ID, active, sort order. +- `ServiceCategory`: UUID ID, optional parent UUID, stable slug, active, sort order, timestamps. +- `ServiceCategoryTranslation`: edge schema between category and locale; `field.ID("category_id", "locale")`; name/optional description. +- `SpokenLanguage`: string ID, active, sort order. +- `SpokenLanguageTranslation`: edge schema between language and locale; composite ID. +- `AdministrativeArea`: UUID ID, source/version/external code/kind/name/optional parent/active/timestamps. +- `Locality`: UUID ID, slug/name/parent parish/source/type/source ID/latitude/longitude/active/timestamps. SQL adds generated stored `center geography(Point,4326)` from longitude/latitude. +- `ProviderProfile`: internal-user UUID ID stored as `internal_user_id`; provider type, display name, bio, primary locality ID, radius, three service mode booleans, timestamps. +- `ProviderServiceLocality`: edge schema with composite `(internal_user_id, locality_id)`. +- `ProviderSpokenLanguage`: edge schema with composite `(internal_user_id, language_code)`. + +- [ ] **Step 1: Write RED schema/migration tests.** + +Assert every table, FK/delete action, composite key, unique slug/code, hierarchy kind check, coordinate/radius/name/bio bounds, at-least-one-service-mode check, generated PostGIS center expression, seed counts/slugs/translations, exact CAOP codes, exact five locality source IDs, and prohibited profile/contact/identity columns. + +- [ ] **Step 2: Run RED.** + +```bash +cd backend +go test ./ent/schema ./internal/users -run 'Test.*(Taxonomy|Location|Language|ProviderProfile|Migration)' -count=1 +``` + +Expected: missing schema/migration contracts. + +- [ ] **Step 3: Add one forward migration.** + +Enable PostGIS if absent; create tables/constraints/indexes; insert deterministic UUIDs and manifest values. The API runtime does not execute this migration. + +- [ ] **Step 4: Implement Ent schemas and composite edge schemas.** + +Use installed Ent `field.ID` on edge schemas. Add inverse/through edges only where needed by typed provider transactions. Do not model the generated `center` column as mutable Ent state. + +- [ ] **Step 5: Generate and run GREEN.** + +```bash +cd backend +go generate ./ent +go test ./ent/schema ./internal/users -run 'Test.*(Taxonomy|Location|Language|ProviderProfile|Migration)' -count=1 +``` + +- [ ] **Step 6: Apply the complete migration chain to local Supabase.** + +```bash +supabase migration up --local +``` + +Verify the migration ledger and seed counts through process-only DB configuration; do not print the URL. + +- [ ] **Step 7: Commit.** + +```bash +git add supabase/migrations backend/ent backend/internal/users/migration_contract_test.go backend/go.sum +git commit -m "feat: add provider marketplace reference schema" +``` + +## Task 3: Implement reference catalog and PostGIS radius service + +**Files:** +- Create `backend/internal/reference/{model,repository,sql_repository,service}.go` and tests. + +**Interfaces:** + +```go +type Repository interface { + Categories(context.Context, string) ([]Category, error) + Languages(context.Context, string) ([]Language, error) + Localities(context.Context, string) ([]Locality, error) + NearbyLocalities(context.Context, uuid.UUID, int, string) ([]LocalityDistance, error) + ValidateProfileReferences(context.Context, ProfileReferences) error +} + +type Service interface { + Categories(context.Context, string) ([]Category, error) + Languages(context.Context, string) ([]Language, error) + Localities(context.Context, string) ([]Locality, error) + NearbyLocalities(context.Context, uuid.UUID, int, string) ([]LocalityDistance, error) +} +``` + +Public DTOs contain UUID/slug/localized names/hierarchy labels and optional integer distance metres. They contain no raw coordinates or provider rows. + +- [ ] **Step 1: Write RED service tests.** + +Cover supported/unsupported locales, inactive omission, stable sort order, missing origin, radius bounds 1–200, and controlled unavailability. + +- [ ] **Step 2: Write RED real-PostgreSQL tests.** + +Use at least three synthetic localities: two in range and one out of range. Assert boundary inclusion, ascending distance, UUID tie-break, inactive omission, no raw coordinate fields, and multi-parent category translation correctness. + +- [ ] **Step 3: Run RED.** + +```bash +cd backend +go test ./internal/reference -count=1 +``` + +Expected: missing package symbols. + +- [ ] **Step 4: Implement parameterized SQL repository.** + +Reuse the existing `*sql.DB`. Use fixed SQL identifiers and bound values only. Radius SQL uses `ST_DWithin`, `ST_Distance`, active predicates, and `ORDER BY distance_meters, id`. Category queries never apply a global child limit; each category/subcategory contributes once. + +- [ ] **Step 5: Run unit and non-skipping PostgreSQL GREEN.** + +```bash +cd backend +go test ./internal/reference -count=1 +TEST_DATABASE_URL="$DB_URL" go test ./internal/reference -run 'TestSQLRepository|TestRadius' -count=1 +``` + +- [ ] **Step 6: Commit.** + +```bash +git add backend/internal/reference +git commit -m "feat: add marketplace reference catalog" +``` + +## Task 4: Implement provider capability authorization and transactional profiles + +**Files:** +- Create `backend/internal/provideraccess/service.go` and test. +- Create `backend/internal/providers/{model,repository,ent_repository,service}.go` and tests. + +**Interfaces:** + +```go +type InternalUserReconciler interface { + Reconcile(context.Context, users.VerifiedIdentity) (users.InternalUser, bool, error) +} + +type CapabilityReader interface { + Get(context.Context, users.VerifiedIdentity) (accounts.Account, error) +} + +type ProviderAuthorizer interface { + RequireProvider(context.Context, users.VerifiedIdentity) (users.InternalUser, error) +} + +type Service interface { + Get(context.Context, users.VerifiedIdentity) (*Profile, error) + Put(context.Context, users.VerifiedIdentity, ReplaceProfile) (Profile, error) +} +``` + +`ReplaceProfile` contains only approved profile fields and reference IDs/codes. Repository methods accept the already-authorized internal user UUID; no transport can provide it. + +- [ ] **Step 1: Write provider-access RED tests.** + +Invalid identity, unavailable account, provider-disabled, and provider-enabled cases; prove no repository/profile call occurs before authorization. + +- [ ] **Step 2: Write provider service RED tests.** + +Cover every scalar bound, trim behavior, provider enum, duplicate/size-constrained locality/language lists, primary-locality membership, at-least-one mode, zero-radius rule, inactive/missing references, owner-only lookup, missing profile, create/update, and controlled failures. + +- [ ] **Step 3: Write transaction/concurrency RED tests.** + +Against PostgreSQL, prove full replacement commits scalars plus exact child sets, invalid child rolls back all changes, repeat is idempotent, concurrent first PUT yields one owner profile, and one user's ID cannot retrieve/update another's profile. + +- [ ] **Step 4: Implement minimal services and Ent repository.** + +Use one Ent transaction for profile and edge-schema replacement. Reload canonical state before commit; normalize timestamps to UTC microseconds. Retry only the documented unique-owner conflict by loading the winner; do not retry validation/FK errors. + +- [ ] **Step 5: Run GREEN.** + +```bash +cd backend +go test ./internal/provideraccess ./internal/providers -count=1 +TEST_DATABASE_URL="$DB_URL" go test -race ./internal/providers -run 'TestEntRepository|TestConcurrent|TestTransaction' -count=1 +``` + +- [ ] **Step 6: Commit.** + +```bash +git add backend/internal/provideraccess backend/internal/providers +git commit -m "feat: add owner-only provider profiles" +``` + +## Task 5: Publish Go/OpenAPI reference and profile contracts + +**Files:** +- Modify `openapi/juntly-api.v1.yaml`. +- Create reference/profile handlers and tests. +- Modify router/OpenAPI contract tests and `backend/cmd/api/main.go`. +- Regenerate `frontend/src/shared/api/generated/**`. + +**Operations:** + +```text +GET /api/v1/catalog/categories +GET /api/v1/reference/localities +GET /api/v1/reference/languages +GET /api/v1/me/provider-profile +PUT /api/v1/me/provider-profile +``` + +Reference operations accept locale; localities optionally accept paired `nearLocalityId` and `radiusKm`. Profile GET returns `{ "profile": null }` before onboarding or a closed owner-only profile. PUT accepts the exact spec request. + +- [ ] **Step 1: Write RED OpenAPI/handler/router tests.** + +Cover strict query/body parsing, locale/radius pairing, null/unknown/duplicate/bounds cases, no service call on invalid input, public reference routes, protected profile routes, `401`, `403 FORBIDDEN`, `400`, `503`, correlation parity, and output privacy. + +- [ ] **Step 2: Run RED.** + +```bash +cd backend +go test ./internal/httpapi -run 'Test.*(Category|Locality|Language|ProviderProfile|OpenAPI|Router)' -count=1 +``` + +- [ ] **Step 3: Implement handlers and dependency composition.** + +Use `url.Values` exact allowlisting for reference queries and `json.Decoder.DisallowUnknownFields()` plus second-value EOF checks for profile PUT. Add only `INVALID_REQUEST`, `FORBIDDEN`, and existing safe codes. + +- [ ] **Step 4: Extend OpenAPI closed schemas and regenerate.** + +```bash +cd frontend +npm run codegen +npm run codegen:check +``` + +- [ ] **Step 5: Run GREEN and cross-layer compile.** + +```bash +cd backend && go test ./internal/httpapi ./cmd/api -count=1 +cd ../frontend && npm run typecheck +``` + +- [ ] **Step 6: Commit.** + +```bash +git add openapi backend/internal/httpapi backend/cmd/api frontend/src/shared/api/generated +git commit -m "feat: add provider reference and profile API" +``` + +## Task 6: Add same-origin BFF routes + +**Files:** +- Create four BFF route files and matching tests listed in the file map. + +- [ ] **Step 1: Write RED reference BFF tests.** + +Prove locale/query allowlisting, generated-client invocation, exact public response parsing, correlation parity, attribution preservation, and topology-safe failure. + +- [ ] **Step 2: Write RED provider BFF tests.** + +Prove signed-out/no-token `401`, server-obtained bearer only, exact PUT object/arrays, null/unknown/duplicate/bound rejection before upstream, preserved `403`, and generic upstream/malformed/correlation `503` without origin/token/private data. + +- [ ] **Step 3: Run RED.** + +```bash +cd frontend +npm test -- src/app/api/v1/catalog/categories/route.test.ts src/app/api/v1/reference/localities/route.test.ts src/app/api/v1/reference/languages/route.test.ts src/app/api/v1/me/provider-profile/route.test.ts +``` + +- [ ] **Step 4: Implement thin generated-client BFF routes.** + +No reference route imports Clerk. Profile routes use `await auth()` and `getToken()` only. Extract shared request-ID/error helpers only after all focused tests are green and duplication is exact. + +- [ ] **Step 5: Run GREEN and full typecheck.** + +```bash +cd frontend +npm test -- src/app/api/v1/catalog/categories/route.test.ts src/app/api/v1/reference/localities/route.test.ts src/app/api/v1/reference/languages/route.test.ts src/app/api/v1/me/provider-profile/route.test.ts +npm run typecheck +``` + +- [ ] **Step 6: Commit.** + +```bash +git add frontend/src/app/api/v1/catalog frontend/src/app/api/v1/reference frontend/src/app/api/v1/me/provider-profile +git commit -m "feat: add provider profile BFF" +``` + +## Task 7: Add localized owner-only provider onboarding UI + +**Files:** +- Create provider form/page/tests. +- Modify account capability card/test to expose the onboarding link only when enabled. +- Modify three locale JSON files and message test. + +- [ ] **Step 1: Write RED page/form/i18n tests.** + +Cover protected dynamic page, provider-disabled `403`/safe state, reference loading, empty profile, all fields, accessible grouped controls, primary locality inclusion, duplicate prevention, client validation, saving lock, stale completion rejection, successful create/update, controlled error/retry, no identity/contact/coordinates in DOM, OSM attribution, pt-PT/en/es key parity, and 44×44px control classes. + +- [ ] **Step 2: Run RED.** + +```bash +cd frontend +npm test -- src/features/provider/provider-profile-form.test.tsx src/app/'[locale]'/account/provider-profile/page.test.tsx src/features/account/account-capabilities-card.test.tsx src/i18n/messages.test.ts +``` + +- [ ] **Step 3: Implement minimal UI.** + +Use only same-origin BFF fetches. Keep public source options in local state and profile mutation generation separate. Disable all dismissal/submit/toggle surfaces during save; apply a completion only when its generation still owns the rendered profile. + +- [ ] **Step 4: Run GREEN, lint, and typecheck.** + +```bash +cd frontend +npm test -- src/features/provider/provider-profile-form.test.tsx src/app/'[locale]'/account/provider-profile/page.test.tsx src/features/account/account-capabilities-card.test.tsx src/i18n/messages.test.ts +npm run lint +npm run typecheck +``` + +- [ ] **Step 5: Commit.** + +```bash +git add frontend/src/features/provider frontend/src/features/account/account-capabilities-card* frontend/src/app/'[locale]'/account/provider-profile frontend/messages frontend/src/i18n/messages.test.ts +git commit -m "feat: add provider profile onboarding" +``` + +## Task 8: Complete Slice 2 acceptance and delivery + +- [ ] **Step 1: Reapply the complete local migration ledger.** + +Start/reuse only Juntly's isolated Supabase stack and apply migrations. Verify reference seed counts/source IDs without printing DB configuration. + +- [ ] **Step 2: Run non-skipping database/race gates.** + +```bash +TEST_DATABASE_URL="$DB_URL" go test -race ./internal/reference ./internal/providers -count=1 +``` + +- [ ] **Step 3: Run complete backend gates.** + +```bash +cd backend +go test ./... +go vet ./... +go build -o "$LOCALAPPDATA/Temp/juntly-api-slice2.exe" ./cmd/api +``` + +- [ ] **Step 4: Run complete frontend/topology gates.** + +```bash +cd frontend && npm run verify +cd .. && docker compose config >/dev/null && git diff --check && git diff --cached --check +``` + +- [ ] **Step 5: Run live public reference proof.** + +Prove category/language/locality responses and radius ordering through same-origin BFF. Record only status, correlation parity, counts/slugs/source attribution, in-range ordering, and absence of coordinates/private fields. + +- [ ] **Step 6: Run real authenticated provider journey.** + +With the approved Clerk development session and process-only local clock skew only if Windows remains unsynchronized: + +```text +provider capability enabled +→ empty provider profile GET +→ strict PUT create +→ GET same owner state +→ PUT replacement +→ GET replaced state +→ fresh page render +``` + +Record only status/correlation/schema, provider type, service-mode booleans, radius, language/service-area counts, stable owner/profile timestamp properties, and no raw identity/contact/coordinate values. + +- [ ] **Step 7: Freeze and review.** + +Synchronize CodeGraph; stage the explicit Slice 2 allowlist; run fixture-aware secret/privacy scans; record binary digest/path count. Request one independent read-only contract/security review if the Codex environment is available. If unavailable for the already documented local cache/MCP reason, record that honestly and perform the repository-required direct review without claiming independence. + +- [ ] **Step 8: Commit final acceptance correction only if needed.** + +Any runtime-found defect gets a new RED test, minimal fix, affected/full gates, new digest, and local commit. Otherwise make no empty commit. + +- [ ] **Step 9: Cleanup and close.** + +Stop tracked frontend/API processes, remove temporary binaries/source downloads, verify project proof ports closed, preserve the Juntly Supabase stack only if immediately needed for the next slice, and require a clean worktree before marking Slice 2 complete. From eb5a076622d4153ab6f66f3537ee2a04e18c2b09 Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 16:43:43 +0100 Subject: [PATCH 14/64] chore: verify launch reference data --- .gitignore | 2 + reference/portugal/launch-area-2025.json | 338 ++++++++++++++++ scripts/build_launch_reference.py | 403 +++++++++++++++++++ scripts/tests/test_build_launch_reference.py | 228 +++++++++++ 4 files changed, 971 insertions(+) create mode 100644 reference/portugal/launch-area-2025.json create mode 100644 scripts/build_launch_reference.py create mode 100644 scripts/tests/test_build_launch_reference.py diff --git a/.gitignore b/.gitignore index 0bf12fb..5c385db 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ dist/ build/ coverage/ *.tsbuildinfo +__pycache__/ +*.py[cod] # Go and test output bin/ diff --git a/reference/portugal/launch-area-2025.json b/reference/portugal/launch-area-2025.json new file mode 100644 index 0000000..4330bae --- /dev/null +++ b/reference/portugal/launch-area-2025.json @@ -0,0 +1,338 @@ +{ + "administrativeAreas": [ + { + "external_code": "PT", + "kind": "country", + "name": "Portugal", + "parent_code": null + }, + { + "external_code": "05", + "kind": "district", + "name": "Castelo Branco", + "parent_code": "PT" + }, + { + "external_code": "0502", + "kind": "municipality", + "name": "Castelo Branco", + "parent_code": "05" + }, + { + "external_code": "0505", + "kind": "municipality", + "name": "Idanha-a-Nova", + "parent_code": "05" + }, + { + "external_code": "050205", + "kind": "parish", + "name": "Castelo Branco", + "parent_code": "0502" + }, + { + "external_code": "050510", + "kind": "parish", + "name": "Penha Garcia", + "parent_code": "0505" + }, + { + "external_code": "050518", + "kind": "parish", + "name": "União das freguesias de Idanha-a-Nova e Alcafozes", + "parent_code": "0505" + }, + { + "external_code": "050520", + "kind": "parish", + "name": "União das freguesias de Monsanto e Idanha-a-Velha", + "parent_code": "0505" + }, + { + "external_code": "050521", + "kind": "parish", + "name": "União das freguesias de Zebreira e Segura", + "parent_code": "0505" + } + ], + "attribution": { + "text": "© OpenStreetMap contributors", + "url": "https://www.openstreetmap.org/copyright" + }, + "categories": [ + { + "parentSlug": null, + "slug": "home-repairs", + "sortOrder": 10, + "translations": { + "en": "Home repairs", + "es": "Reparaciones del hogar", + "pt-PT": "Reparações domésticas" + } + }, + { + "parentSlug": "home-repairs", + "slug": "plumbing", + "sortOrder": 10, + "translations": { + "en": "Plumbing", + "es": "Fontanería", + "pt-PT": "Canalização" + } + }, + { + "parentSlug": "home-repairs", + "slug": "electrical-work", + "sortOrder": 20, + "translations": { + "en": "Electrical work", + "es": "Electricidad", + "pt-PT": "Eletricidade" + } + }, + { + "parentSlug": "home-repairs", + "slug": "construction", + "sortOrder": 30, + "translations": { + "en": "Construction", + "es": "Construcción", + "pt-PT": "Construção" + } + }, + { + "parentSlug": "home-repairs", + "slug": "small-repairs", + "sortOrder": 40, + "translations": { + "en": "Small repairs", + "es": "Pequeñas reparaciones", + "pt-PT": "Pequenas reparações" + } + }, + { + "parentSlug": null, + "slug": "home-and-garden", + "sortOrder": 20, + "translations": { + "en": "Home and garden", + "es": "Hogar y jardín", + "pt-PT": "Casa e jardim" + } + }, + { + "parentSlug": "home-and-garden", + "slug": "cleaning", + "sortOrder": 10, + "translations": { + "en": "Cleaning", + "es": "Limpieza", + "pt-PT": "Limpeza" + } + }, + { + "parentSlug": "home-and-garden", + "slug": "gardening", + "sortOrder": 20, + "translations": { + "en": "Gardening", + "es": "Jardinería", + "pt-PT": "Jardinagem" + } + }, + { + "parentSlug": null, + "slug": "rural-and-transport", + "sortOrder": 30, + "translations": { + "en": "Rural services and transport", + "es": "Servicios rurales y transporte", + "pt-PT": "Serviços rurais e transporte" + } + }, + { + "parentSlug": "rural-and-transport", + "slug": "agricultural-assistance", + "sortOrder": 10, + "translations": { + "en": "Agricultural assistance", + "es": "Ayuda agrícola", + "pt-PT": "Apoio agrícola" + } + }, + { + "parentSlug": "rural-and-transport", + "slug": "transport", + "sortOrder": 20, + "translations": { + "en": "Transport", + "es": "Transporte", + "pt-PT": "Transporte" + } + }, + { + "parentSlug": null, + "slug": "care-and-learning", + "sortOrder": 40, + "translations": { + "en": "Care and learning", + "es": "Cuidados y aprendizaje", + "pt-PT": "Cuidados e aprendizagem" + } + }, + { + "parentSlug": "care-and-learning", + "slug": "elderly-assistance", + "sortOrder": 10, + "translations": { + "en": "Elderly assistance", + "es": "Ayuda a mayores", + "pt-PT": "Apoio a idosos" + } + }, + { + "parentSlug": "care-and-learning", + "slug": "animal-care", + "sortOrder": 20, + "translations": { + "en": "Animal care", + "es": "Cuidado de animales", + "pt-PT": "Cuidados de animais" + } + }, + { + "parentSlug": "care-and-learning", + "slug": "private-lessons", + "sortOrder": 30, + "translations": { + "en": "Private lessons", + "es": "Clases particulares", + "pt-PT": "Aulas particulares" + } + }, + { + "parentSlug": null, + "slug": "food-and-technology", + "sortOrder": 50, + "translations": { + "en": "Food and technology", + "es": "Alimentación y tecnología", + "pt-PT": "Alimentação e tecnologia" + } + }, + { + "parentSlug": "food-and-technology", + "slug": "meal-preparation", + "sortOrder": 10, + "translations": { + "en": "Meal preparation", + "es": "Preparación de comidas", + "pt-PT": "Preparação de refeições" + } + }, + { + "parentSlug": "food-and-technology", + "slug": "computer-repair", + "sortOrder": 20, + "translations": { + "en": "Computer repair", + "es": "Reparación de ordenadores", + "pt-PT": "Reparação de computadores" + } + } + ], + "languages": [ + { + "code": "pt-PT", + "sortOrder": 10, + "translations": { + "en": "Portuguese", + "es": "Portugués", + "pt-PT": "Português" + } + }, + { + "code": "en", + "sortOrder": 20, + "translations": { + "en": "English", + "es": "Inglés", + "pt-PT": "Inglês" + } + }, + { + "code": "es", + "sortOrder": 30, + "translations": { + "en": "Spanish", + "es": "Español", + "pt-PT": "Espanhol" + } + } + ], + "localities": [ + { + "latitude": "39.8266322", + "longitude": "-7.4919318", + "name": "Castelo Branco", + "parent_code": "050205", + "slug": "castelo-branco", + "source_element_id": "R5396187" + }, + { + "latitude": "39.9260883", + "longitude": "-7.2436356", + "name": "Idanha-a-Nova", + "parent_code": "050518", + "slug": "idanha-a-nova", + "source_element_id": "R5395738" + }, + { + "latitude": "40.0387510", + "longitude": "-7.1151133", + "name": "Monsanto", + "parent_code": "050520", + "slug": "monsanto", + "source_element_id": "N371426674" + }, + { + "latitude": "40.0422569", + "longitude": "-7.0163521", + "name": "Penha Garcia", + "parent_code": "050510", + "slug": "penha-garcia", + "source_element_id": "R5431477" + }, + { + "latitude": "39.8455920", + "longitude": "-7.0703366", + "name": "Zebreira", + "parent_code": "050521", + "slug": "zebreira", + "source_element_id": "N440173641" + } + ], + "retrievalDate": "2026-08-23", + "source": { + "caop": { + "layers": [ + "cont_distritos", + "cont_municipios", + "cont_freguesias" + ], + "sha256": "87cd67f4b1fbadf23d9324e6fb231ff05531e4db347af36ccc7c6cbabe3ecd1d", + "url": "https://geo2.dgterritorio.gov.pt/caop/CAOP_Continente_2025-gpkg.zip", + "version": "2025" + }, + "openStreetMap": { + "license": "https://www.openstreetmap.org/copyright", + "nominatimPolicy": "https://operations.osmfoundation.org/policies/nominatim" + } + }, + "supportedLocales": [ + "pt-PT", + "en", + "es" + ], + "version": 1 +} diff --git a/scripts/build_launch_reference.py b/scripts/build_launch_reference.py new file mode 100644 index 0000000..2c01522 --- /dev/null +++ b/scripts/build_launch_reference.py @@ -0,0 +1,403 @@ +from __future__ import annotations + +import argparse +import hashlib +import json +import shutil +import sqlite3 +import tempfile +import time +import urllib.parse +import urllib.request +import zipfile +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Callable, Sequence + +CAOP_URL = "https://geo2.dgterritorio.gov.pt/caop/CAOP_Continente_2025-gpkg.zip" +CAOP_SHA256 = "87cd67f4b1fbadf23d9324e6fb231ff05531e4db347af36ccc7c6cbabe3ecd1d" +SOURCE_VERSION = "2025" +RETRIEVAL_DATE = "2026-08-23" +NOMINATIM_URL = "https://nominatim.openstreetmap.org/search" +USER_AGENT = "JuntlyReferenceVerifier/1.0 (SourceSensei; one-time launch data verification)" + + +class ReferenceDataError(ValueError): + pass + + +@dataclass(frozen=True) +class RequiredArea: + kind: str + external_code: str + name: str + parent_code: str | None + table: str | None + code_column: str | None + name_column: str | None + + +@dataclass(frozen=True) +class AdministrativeArea: + kind: str + external_code: str + name: str + parent_code: str | None + + +@dataclass(frozen=True) +class FrozenLocality: + slug: str + name: str + query: str + parent_code: str + osm_type: str + osm_id: int + + +@dataclass(frozen=True) +class Locality: + slug: str + name: str + parent_code: str + source_element_id: str + latitude: str + longitude: str + + +REQUIRED_AREAS = ( + RequiredArea("country", "PT", "Portugal", None, None, None, None), + RequiredArea("district", "05", "Castelo Branco", "PT", "cont_distritos", "dt", "distrito"), + RequiredArea("municipality", "0502", "Castelo Branco", "05", "cont_municipios", "dtmn", "municipio"), + RequiredArea("municipality", "0505", "Idanha-a-Nova", "05", "cont_municipios", "dtmn", "municipio"), + RequiredArea("parish", "050205", "Castelo Branco", "0502", "cont_freguesias", "dtmnfr", "freguesia"), + RequiredArea("parish", "050510", "Penha Garcia", "0505", "cont_freguesias", "dtmnfr", "freguesia"), + RequiredArea( + "parish", + "050518", + "União das freguesias de Idanha-a-Nova e Alcafozes", + "0505", + "cont_freguesias", + "dtmnfr", + "freguesia", + ), + RequiredArea( + "parish", + "050520", + "União das freguesias de Monsanto e Idanha-a-Velha", + "0505", + "cont_freguesias", + "dtmnfr", + "freguesia", + ), + RequiredArea( + "parish", + "050521", + "União das freguesias de Zebreira e Segura", + "0505", + "cont_freguesias", + "dtmnfr", + "freguesia", + ), +) + +FROZEN_LOCALITIES = ( + FrozenLocality( + "castelo-branco", + "Castelo Branco", + "Castelo Branco, Castelo Branco, Portugal", + "050205", + "relation", + 5396187, + ), + FrozenLocality( + "idanha-a-nova", + "Idanha-a-Nova", + "Idanha-a-Nova, Castelo Branco, Portugal", + "050518", + "relation", + 5395738, + ), + FrozenLocality( + "zebreira", + "Zebreira", + "Zebreira, Idanha-a-Nova, Portugal", + "050521", + "node", + 440173641, + ), + FrozenLocality( + "penha-garcia", + "Penha Garcia", + "Penha Garcia, Idanha-a-Nova, Portugal", + "050510", + "relation", + 5431477, + ), + FrozenLocality( + "monsanto", + "Monsanto", + "Monsanto, Idanha-a-Nova, Portugal", + "050520", + "node", + 371426674, + ), +) + +CATEGORY_SEED = ( + ( + "home-repairs", + None, + 10, + ("Reparações domésticas", "Home repairs", "Reparaciones del hogar"), + ), + ("plumbing", "home-repairs", 10, ("Canalização", "Plumbing", "Fontanería")), + ("electrical-work", "home-repairs", 20, ("Eletricidade", "Electrical work", "Electricidad")), + ("construction", "home-repairs", 30, ("Construção", "Construction", "Construcción")), + ("small-repairs", "home-repairs", 40, ("Pequenas reparações", "Small repairs", "Pequeñas reparaciones")), + ("home-and-garden", None, 20, ("Casa e jardim", "Home and garden", "Hogar y jardín")), + ("cleaning", "home-and-garden", 10, ("Limpeza", "Cleaning", "Limpieza")), + ("gardening", "home-and-garden", 20, ("Jardinagem", "Gardening", "Jardinería")), + ("rural-and-transport", None, 30, ("Serviços rurais e transporte", "Rural services and transport", "Servicios rurales y transporte")), + ("agricultural-assistance", "rural-and-transport", 10, ("Apoio agrícola", "Agricultural assistance", "Ayuda agrícola")), + ("transport", "rural-and-transport", 20, ("Transporte", "Transport", "Transporte")), + ("care-and-learning", None, 40, ("Cuidados e aprendizagem", "Care and learning", "Cuidados y aprendizaje")), + ("elderly-assistance", "care-and-learning", 10, ("Apoio a idosos", "Elderly assistance", "Ayuda a mayores")), + ("animal-care", "care-and-learning", 20, ("Cuidados de animais", "Animal care", "Cuidado de animales")), + ("private-lessons", "care-and-learning", 30, ("Aulas particulares", "Private lessons", "Clases particulares")), + ("food-and-technology", None, 50, ("Alimentação e tecnologia", "Food and technology", "Alimentación y tecnología")), + ("meal-preparation", "food-and-technology", 10, ("Preparação de refeições", "Meal preparation", "Preparación de comidas")), + ("computer-repair", "food-and-technology", 20, ("Reparação de computadores", "Computer repair", "Reparación de ordenadores")), +) + +LANGUAGE_SEED = ( + ("pt-PT", 10, ("Português", "Portuguese", "Portugués")), + ("en", 20, ("Inglês", "English", "Inglés")), + ("es", 30, ("Espanhol", "Spanish", "Español")), +) + +LOCALES = ("pt-PT", "en", "es") + + +def verify_caop_archive(path: Path, expected_sha256: str = CAOP_SHA256) -> None: + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) + if digest.hexdigest() != expected_sha256: + raise ReferenceDataError("CAOP checksum mismatch") + + +def extract_administrative_rows( + archive: Path, required: Sequence[RequiredArea] +) -> list[AdministrativeArea]: + with tempfile.TemporaryDirectory(prefix="juntly-caop-extract-") as directory: + root = Path(directory) + with zipfile.ZipFile(archive) as zipped: + packages = [name for name in zipped.namelist() if name.lower().endswith(".gpkg")] + if len(packages) != 1: + raise ReferenceDataError("CAOP archive must contain exactly one GeoPackage") + zipped.extract(packages[0], root) + connection = sqlite3.connect(root / packages[0]) + try: + rows: list[AdministrativeArea] = [] + for area in required: + if area.table is None: + rows.append( + AdministrativeArea( + area.kind, area.external_code, area.name, area.parent_code + ) + ) + continue + table = _allowlisted_identifier(area.table) + code_column = _allowlisted_identifier(area.code_column or "") + name_column = _allowlisted_identifier(area.name_column or "") + matches = connection.execute( + f'SELECT "{code_column}", "{name_column}" FROM "{table}" ' + f'WHERE "{code_column}" = ? AND "{name_column}" = ?', + (area.external_code, area.name), + ).fetchall() + if len(matches) != 1: + raise ReferenceDataError( + f"required administrative row {area.external_code} must exist exactly once" + ) + rows.append( + AdministrativeArea( + area.kind, area.external_code, area.name, area.parent_code + ) + ) + return rows + finally: + connection.close() + + +def resolve_localities( + resolve: Callable[[str], list[dict[str, object]]], + sleep: Callable[[float], None] = time.sleep, +) -> list[Locality]: + localities: list[Locality] = [] + for index, frozen in enumerate(FROZEN_LOCALITIES): + if index: + sleep(1.05) + results = resolve(frozen.query) + matches = [ + item + for item in results + if item.get("osm_type") == frozen.osm_type + and item.get("osm_id") == frozen.osm_id + ] + if len(matches) != 1: + raise ReferenceDataError( + f"frozen locality {frozen.slug} must resolve exactly once" + ) + result = matches[0] + latitude = _coordinate(result.get("lat"), -90, 90, "latitude") + longitude = _coordinate(result.get("lon"), -180, 180, "longitude") + prefix = "N" if frozen.osm_type == "node" else "R" + localities.append( + Locality( + slug=frozen.slug, + name=frozen.name, + parent_code=frozen.parent_code, + source_element_id=f"{prefix}{frozen.osm_id}", + latitude=latitude, + longitude=longitude, + ) + ) + return sorted(localities, key=lambda item: item.slug) + + +def build_manifest( + caop_zip: Path, + resolve: Callable[[str], list[dict[str, object]]], + sleep: Callable[[float], None] = time.sleep, + expected_sha256: str = CAOP_SHA256, +) -> dict[str, object]: + verify_caop_archive(caop_zip, expected_sha256) + areas = extract_administrative_rows(caop_zip, REQUIRED_AREAS) + localities = resolve_localities(resolve, sleep) + return { + "version": 1, + "retrievalDate": RETRIEVAL_DATE, + "source": { + "caop": { + "url": CAOP_URL, + "version": SOURCE_VERSION, + "sha256": expected_sha256, + "layers": ["cont_distritos", "cont_municipios", "cont_freguesias"], + }, + "openStreetMap": { + "nominatimPolicy": "https://operations.osmfoundation.org/policies/nominatim", + "license": "https://www.openstreetmap.org/copyright", + }, + }, + "attribution": { + "text": "© OpenStreetMap contributors", + "url": "https://www.openstreetmap.org/copyright", + }, + "supportedLocales": list(LOCALES), + "administrativeAreas": [asdict(area) for area in areas], + "localities": [asdict(locality) for locality in localities], + "categories": [ + { + "slug": slug, + "parentSlug": parent, + "sortOrder": order, + "translations": dict(zip(LOCALES, names, strict=True)), + } + for slug, parent, order, names in CATEGORY_SEED + ], + "languages": [ + { + "code": code, + "sortOrder": order, + "translations": dict(zip(LOCALES, names, strict=True)), + } + for code, order, names in LANGUAGE_SEED + ], + } + + +def resolve_nominatim(query: str) -> list[dict[str, object]]: + url = NOMINATIM_URL + "?" + urllib.parse.urlencode( + {"q": query, "format": "jsonv2", "limit": "3", "countrycodes": "pt"} + ) + request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) + with urllib.request.urlopen(request, timeout=30) as response: + payload = json.load(response) + if not isinstance(payload, list): + raise ReferenceDataError("Nominatim response must be a list") + return payload + + +def download_caop(destination: Path) -> None: + request = urllib.request.Request(CAOP_URL, headers={"User-Agent": USER_AGENT}) + with urllib.request.urlopen(request, timeout=120) as response, destination.open( + "wb" + ) as output: + shutil.copyfileobj(response, output) + + +def _allowlisted_identifier(value: str) -> str: + allowed = { + "cont_distritos", + "cont_municipios", + "cont_freguesias", + "dt", + "dtmn", + "dtmnfr", + "distrito", + "municipio", + "freguesia", + } + if value not in allowed: + raise ReferenceDataError("unexpected CAOP identifier") + return value + + +def _coordinate(value: object, minimum: int, maximum: int, label: str) -> str: + if not isinstance(value, str): + raise ReferenceDataError(f"{label} must be a string") + try: + numeric = float(value) + except ValueError as error: + raise ReferenceDataError(f"invalid {label}") from error + if not minimum <= numeric <= maximum: + raise ReferenceDataError(f"invalid {label}") + return value + + +def _write_json(path: Path, manifest: dict[str, object]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + + +def main() -> int: + parser = argparse.ArgumentParser() + mode = parser.add_mutually_exclusive_group(required=True) + mode.add_argument("--write", type=Path) + mode.add_argument("--verify", type=Path) + args = parser.parse_args() + + temporary_root = Path(tempfile.mkdtemp(prefix="juntly-launch-reference-")) + try: + archive = temporary_root / "caop-2025.zip" + download_caop(archive) + manifest = build_manifest(archive, resolve_nominatim) + target: Path = args.write or args.verify + if args.write: + _write_json(target, manifest) + return 0 + expected = json.loads(target.read_text(encoding="utf-8")) + if manifest != expected: + raise ReferenceDataError("checked-in launch manifest does not match sources") + return 0 + finally: + shutil.rmtree(temporary_root, ignore_errors=True) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/tests/test_build_launch_reference.py b/scripts/tests/test_build_launch_reference.py new file mode 100644 index 0000000..670b203 --- /dev/null +++ b/scripts/tests/test_build_launch_reference.py @@ -0,0 +1,228 @@ +from __future__ import annotations + +import hashlib +import json +import sqlite3 +import tempfile +import unittest +import zipfile +from pathlib import Path + +from scripts.build_launch_reference import ( + CAOP_SHA256, + REQUIRED_AREAS, + ReferenceDataError, + build_manifest, + extract_administrative_rows, + resolve_localities, + verify_caop_archive, +) + + +class LaunchReferenceBuilderTests(unittest.TestCase): + def test_rejects_caop_checksum_mismatch(self) -> None: + with tempfile.TemporaryDirectory() as directory: + archive = self._create_archive(Path(directory)) + + with self.assertRaisesRegex(ReferenceDataError, "checksum"): + verify_caop_archive(archive, "0" * 64) + + def test_extracts_each_required_administrative_row_once_in_stable_order(self) -> None: + with tempfile.TemporaryDirectory() as directory: + archive = self._create_archive(Path(directory)) + + rows = extract_administrative_rows(archive, REQUIRED_AREAS) + + self.assertEqual( + [(row.kind, row.external_code, row.name, row.parent_code) for row in rows], + [ + ("country", "PT", "Portugal", None), + ("district", "05", "Castelo Branco", "PT"), + ("municipality", "0502", "Castelo Branco", "05"), + ("municipality", "0505", "Idanha-a-Nova", "05"), + ("parish", "050205", "Castelo Branco", "0502"), + ("parish", "050510", "Penha Garcia", "0505"), + ( + "parish", + "050518", + "União das freguesias de Idanha-a-Nova e Alcafozes", + "0505", + ), + ( + "parish", + "050520", + "União das freguesias de Monsanto e Idanha-a-Velha", + "0505", + ), + ( + "parish", + "050521", + "União das freguesias de Zebreira e Segura", + "0505", + ), + ], + ) + + def test_rejects_missing_or_duplicate_administrative_rows(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + missing = self._create_archive(root / "missing", omit_code="050510") + duplicate = self._create_archive(root / "duplicate", duplicate_code="050510") + + with self.assertRaisesRegex(ReferenceDataError, "exactly once"): + extract_administrative_rows(missing, REQUIRED_AREAS) + with self.assertRaisesRegex(ReferenceDataError, "exactly once"): + extract_administrative_rows(duplicate, REQUIRED_AREAS) + + def test_resolves_only_frozen_osm_elements_and_spaces_requests(self) -> None: + calls: list[str] = [] + sleeps: list[float] = [] + + def resolver(query: str) -> list[dict[str, object]]: + calls.append(query) + frozen = { + "Castelo Branco, Castelo Branco, Portugal": ( + "relation", + 5396187, + "39.8266322", + "-7.4919318", + ), + "Idanha-a-Nova, Castelo Branco, Portugal": ( + "relation", + 5395738, + "39.9260883", + "-7.2436356", + ), + "Zebreira, Idanha-a-Nova, Portugal": ( + "node", + 440173641, + "39.8455920", + "-7.0703366", + ), + "Penha Garcia, Idanha-a-Nova, Portugal": ( + "relation", + 5431477, + "40.0422569", + "-7.0163521", + ), + "Monsanto, Idanha-a-Nova, Portugal": ( + "node", + 371426674, + "40.0387510", + "-7.1151133", + ), + } + osm_type, osm_id, latitude, longitude = frozen[query] + return [ + { + "osm_type": "node", + "osm_id": 1, + "lat": "0", + "lon": "0", + "display_name": "unreviewed candidate", + }, + { + "osm_type": osm_type, + "osm_id": osm_id, + "lat": latitude, + "lon": longitude, + "display_name": query, + }, + ] + + localities = resolve_localities(resolver, sleeps.append) + + self.assertEqual(len(calls), 5) + self.assertEqual(sleeps, [1.05, 1.05, 1.05, 1.05]) + self.assertEqual( + [(item.slug, item.source_element_id) for item in localities], + [ + ("castelo-branco", "R5396187"), + ("idanha-a-nova", "R5395738"), + ("monsanto", "N371426674"), + ("penha-garcia", "R5431477"), + ("zebreira", "N440173641"), + ], + ) + + def test_build_manifest_is_deterministic_and_excludes_raw_source_data(self) -> None: + with tempfile.TemporaryDirectory() as directory: + archive = self._create_archive(Path(directory)) + digest = hashlib.sha256(archive.read_bytes()).hexdigest() + + def resolver(query: str) -> list[dict[str, object]]: + by_query = { + "Castelo Branco, Castelo Branco, Portugal": ("relation", 5396187, "39.8266322", "-7.4919318"), + "Idanha-a-Nova, Castelo Branco, Portugal": ("relation", 5395738, "39.9260883", "-7.2436356"), + "Zebreira, Idanha-a-Nova, Portugal": ("node", 440173641, "39.8455920", "-7.0703366"), + "Penha Garcia, Idanha-a-Nova, Portugal": ("relation", 5431477, "40.0422569", "-7.0163521"), + "Monsanto, Idanha-a-Nova, Portugal": ("node", 371426674, "40.0387510", "-7.1151133"), + } + osm_type, osm_id, latitude, longitude = by_query[query] + return [{"osm_type": osm_type, "osm_id": osm_id, "lat": latitude, "lon": longitude}] + + first = build_manifest(archive, resolver, lambda _: None, digest) + second = build_manifest(archive, resolver, lambda _: None, digest) + + self.assertEqual(first, second) + encoded = json.dumps(first, sort_keys=True) + self.assertNotIn("postal", encoded.lower()) + self.assertNotIn("display_name", encoded) + self.assertNotIn("raw", encoded.lower()) + self.assertEqual(first["source"]["caop"]["sha256"], digest) + self.assertEqual(len(first["localities"]), 5) + self.assertEqual(first["attribution"]["text"], "© OpenStreetMap contributors") + + def _create_archive( + self, + root: Path, + *, + omit_code: str | None = None, + duplicate_code: str | None = None, + ) -> Path: + root.mkdir(parents=True, exist_ok=True) + package = root / "source.gpkg" + connection = sqlite3.connect(package) + try: + connection.execute("create table cont_distritos (dt text, distrito text)") + connection.execute( + "create table cont_municipios (dtmn text, municipio text, distrito_ilha text)" + ) + connection.execute( + "create table cont_freguesias (dtmnfr text, freguesia text, municipio text, distrito_ilha text)" + ) + connection.execute( + "insert into cont_distritos values (?, ?)", ("05", "Castelo Branco") + ) + connection.executemany( + "insert into cont_municipios values (?, ?, ?)", + (("0502", "Castelo Branco", "Castelo Branco"), ("0505", "Idanha-a-Nova", "Castelo Branco")), + ) + rows = [ + ("050205", "Castelo Branco", "Castelo Branco", "Castelo Branco"), + ("050510", "Penha Garcia", "Idanha-a-Nova", "Castelo Branco"), + ("050518", "União das freguesias de Idanha-a-Nova e Alcafozes", "Idanha-a-Nova", "Castelo Branco"), + ("050520", "União das freguesias de Monsanto e Idanha-a-Velha", "Idanha-a-Nova", "Castelo Branco"), + ("050521", "União das freguesias de Zebreira e Segura", "Idanha-a-Nova", "Castelo Branco"), + ] + connection.executemany( + "insert into cont_freguesias values (?, ?, ?, ?)", + [row for row in rows if row[0] != omit_code], + ) + if duplicate_code: + duplicate = next(row for row in rows if row[0] == duplicate_code) + connection.execute( + "insert into cont_freguesias values (?, ?, ?, ?)", duplicate + ) + connection.commit() + finally: + connection.close() + + archive = root / "source.zip" + with zipfile.ZipFile(archive, "w") as zipped: + zipped.write(package, arcname="source.gpkg") + return archive + + +if __name__ == "__main__": + unittest.main() From 9b0818c6a6c1d99b3bb4b351435befc840bb1fb8 Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 17:00:30 +0100 Subject: [PATCH 15/64] feat: add provider marketplace reference schema --- backend/ent/administrativearea.go | 262 + .../administrativearea/administrativearea.go | 215 + backend/ent/administrativearea/where.go | 631 ++ backend/ent/administrativearea_create.go | 465 ++ backend/ent/administrativearea_delete.go | 88 + backend/ent/administrativearea_query.go | 760 ++ backend/ent/administrativearea_update.go | 632 ++ backend/ent/client.go | 1679 +++- backend/ent/ent.go | 24 +- backend/ent/hook/hook.go | 120 + backend/ent/locality.go | 277 + backend/ent/locality/locality.go | 225 + backend/ent/locality/where.go | 733 ++ backend/ent/locality_create.go | 482 ++ backend/ent/locality_delete.go | 88 + backend/ent/locality_query.go | 713 ++ backend/ent/locality_update.go | 476 ++ backend/ent/migrate/schema.go | 298 + backend/ent/mutation.go | 7333 ++++++++++++++++- backend/ent/predicate/predicate.go | 30 + backend/ent/providerprofile.go | 284 + .../ent/providerprofile/providerprofile.go | 274 + backend/ent/providerprofile/where.go | 599 ++ backend/ent/providerprofile_create.go | 463 ++ backend/ent/providerprofile_delete.go | 88 + backend/ent/providerprofile_query.go | 899 ++ backend/ent/providerprofile_update.go | 887 ++ backend/ent/providerservicelocality.go | 152 + .../providerservicelocality.go | 98 + backend/ent/providerservicelocality/where.go | 121 + backend/ent/providerservicelocality_create.go | 238 + backend/ent/providerservicelocality_delete.go | 87 + backend/ent/providerservicelocality_query.go | 604 ++ backend/ent/providerservicelocality_update.go | 205 + backend/ent/providerspokenlanguage.go | 154 + .../providerspokenlanguage.go | 103 + backend/ent/providerspokenlanguage/where.go | 166 + backend/ent/providerspokenlanguage_create.go | 249 + backend/ent/providerspokenlanguage_delete.go | 87 + backend/ent/providerspokenlanguage_query.go | 604 ++ backend/ent/providerspokenlanguage_update.go | 205 + backend/ent/runtime.go | 563 ++ backend/ent/schema/administrativearea.go | 47 + backend/ent/schema/locality.go | 41 + .../ent/schema/marketplace_reference_test.go | 157 + backend/ent/schema/providerprofile.go | 39 + backend/ent/schema/providerservicelocality.go | 33 + backend/ent/schema/providerspokenlanguage.go | 33 + backend/ent/schema/servicecategory.go | 41 + .../ent/schema/servicecategorytranslation.go | 35 + backend/ent/schema/spokenlanguage.go | 30 + .../ent/schema/spokenlanguagetranslation.go | 33 + backend/ent/schema/supportedlocale.go | 30 + backend/ent/servicecategory.go | 247 + .../ent/servicecategory/servicecategory.go | 219 + backend/ent/servicecategory/where.go | 419 + backend/ent/servicecategory_create.go | 411 + backend/ent/servicecategory_delete.go | 88 + backend/ent/servicecategory_query.go | 867 ++ backend/ent/servicecategory_update.go | 772 ++ backend/ent/servicecategorytranslation.go | 179 + .../servicecategorytranslation.go | 123 + .../ent/servicecategorytranslation/where.go | 316 + .../ent/servicecategorytranslation_create.go | 284 + .../ent/servicecategorytranslation_delete.go | 87 + .../ent/servicecategorytranslation_query.go | 604 ++ .../ent/servicecategorytranslation_update.go | 311 + backend/ent/spokenlanguage.go | 174 + backend/ent/spokenlanguage/spokenlanguage.go | 162 + backend/ent/spokenlanguage/where.go | 208 + backend/ent/spokenlanguage_create.go | 300 + backend/ent/spokenlanguage_delete.go | 88 + backend/ent/spokenlanguage_query.go | 821 ++ backend/ent/spokenlanguage_update.go | 616 ++ backend/ent/spokenlanguagetranslation.go | 162 + .../spokenlanguagetranslation.go | 115 + .../ent/spokenlanguagetranslation/where.go | 280 + .../ent/spokenlanguagetranslation_create.go | 271 + .../ent/spokenlanguagetranslation_delete.go | 87 + .../ent/spokenlanguagetranslation_query.go | 603 ++ .../ent/spokenlanguagetranslation_update.go | 249 + backend/ent/supportedlocale.go | 158 + .../ent/supportedlocale/supportedlocale.go | 130 + backend/ent/supportedlocale/where.go | 185 + backend/ent/supportedlocale_create.go | 300 + backend/ent/supportedlocale_delete.go | 88 + backend/ent/supportedlocale_query.go | 747 ++ backend/ent/supportedlocale_update.go | 616 ++ backend/ent/tx.go | 32 +- .../internal/users/migration_contract_test.go | 82 + ...e_taxonomy_locations_provider_profiles.sql | 225 + 91 files changed, 34617 insertions(+), 189 deletions(-) create mode 100644 backend/ent/administrativearea.go create mode 100644 backend/ent/administrativearea/administrativearea.go create mode 100644 backend/ent/administrativearea/where.go create mode 100644 backend/ent/administrativearea_create.go create mode 100644 backend/ent/administrativearea_delete.go create mode 100644 backend/ent/administrativearea_query.go create mode 100644 backend/ent/administrativearea_update.go create mode 100644 backend/ent/locality.go create mode 100644 backend/ent/locality/locality.go create mode 100644 backend/ent/locality/where.go create mode 100644 backend/ent/locality_create.go create mode 100644 backend/ent/locality_delete.go create mode 100644 backend/ent/locality_query.go create mode 100644 backend/ent/locality_update.go create mode 100644 backend/ent/providerprofile.go create mode 100644 backend/ent/providerprofile/providerprofile.go create mode 100644 backend/ent/providerprofile/where.go create mode 100644 backend/ent/providerprofile_create.go create mode 100644 backend/ent/providerprofile_delete.go create mode 100644 backend/ent/providerprofile_query.go create mode 100644 backend/ent/providerprofile_update.go create mode 100644 backend/ent/providerservicelocality.go create mode 100644 backend/ent/providerservicelocality/providerservicelocality.go create mode 100644 backend/ent/providerservicelocality/where.go create mode 100644 backend/ent/providerservicelocality_create.go create mode 100644 backend/ent/providerservicelocality_delete.go create mode 100644 backend/ent/providerservicelocality_query.go create mode 100644 backend/ent/providerservicelocality_update.go create mode 100644 backend/ent/providerspokenlanguage.go create mode 100644 backend/ent/providerspokenlanguage/providerspokenlanguage.go create mode 100644 backend/ent/providerspokenlanguage/where.go create mode 100644 backend/ent/providerspokenlanguage_create.go create mode 100644 backend/ent/providerspokenlanguage_delete.go create mode 100644 backend/ent/providerspokenlanguage_query.go create mode 100644 backend/ent/providerspokenlanguage_update.go create mode 100644 backend/ent/schema/administrativearea.go create mode 100644 backend/ent/schema/locality.go create mode 100644 backend/ent/schema/marketplace_reference_test.go create mode 100644 backend/ent/schema/providerprofile.go create mode 100644 backend/ent/schema/providerservicelocality.go create mode 100644 backend/ent/schema/providerspokenlanguage.go create mode 100644 backend/ent/schema/servicecategory.go create mode 100644 backend/ent/schema/servicecategorytranslation.go create mode 100644 backend/ent/schema/spokenlanguage.go create mode 100644 backend/ent/schema/spokenlanguagetranslation.go create mode 100644 backend/ent/schema/supportedlocale.go create mode 100644 backend/ent/servicecategory.go create mode 100644 backend/ent/servicecategory/servicecategory.go create mode 100644 backend/ent/servicecategory/where.go create mode 100644 backend/ent/servicecategory_create.go create mode 100644 backend/ent/servicecategory_delete.go create mode 100644 backend/ent/servicecategory_query.go create mode 100644 backend/ent/servicecategory_update.go create mode 100644 backend/ent/servicecategorytranslation.go create mode 100644 backend/ent/servicecategorytranslation/servicecategorytranslation.go create mode 100644 backend/ent/servicecategorytranslation/where.go create mode 100644 backend/ent/servicecategorytranslation_create.go create mode 100644 backend/ent/servicecategorytranslation_delete.go create mode 100644 backend/ent/servicecategorytranslation_query.go create mode 100644 backend/ent/servicecategorytranslation_update.go create mode 100644 backend/ent/spokenlanguage.go create mode 100644 backend/ent/spokenlanguage/spokenlanguage.go create mode 100644 backend/ent/spokenlanguage/where.go create mode 100644 backend/ent/spokenlanguage_create.go create mode 100644 backend/ent/spokenlanguage_delete.go create mode 100644 backend/ent/spokenlanguage_query.go create mode 100644 backend/ent/spokenlanguage_update.go create mode 100644 backend/ent/spokenlanguagetranslation.go create mode 100644 backend/ent/spokenlanguagetranslation/spokenlanguagetranslation.go create mode 100644 backend/ent/spokenlanguagetranslation/where.go create mode 100644 backend/ent/spokenlanguagetranslation_create.go create mode 100644 backend/ent/spokenlanguagetranslation_delete.go create mode 100644 backend/ent/spokenlanguagetranslation_query.go create mode 100644 backend/ent/spokenlanguagetranslation_update.go create mode 100644 backend/ent/supportedlocale.go create mode 100644 backend/ent/supportedlocale/supportedlocale.go create mode 100644 backend/ent/supportedlocale/where.go create mode 100644 backend/ent/supportedlocale_create.go create mode 100644 backend/ent/supportedlocale_delete.go create mode 100644 backend/ent/supportedlocale_query.go create mode 100644 backend/ent/supportedlocale_update.go create mode 100644 supabase/migrations/20260823165327_create_taxonomy_locations_provider_profiles.sql diff --git a/backend/ent/administrativearea.go b/backend/ent/administrativearea.go new file mode 100644 index 0000000..e9d0619 --- /dev/null +++ b/backend/ent/administrativearea.go @@ -0,0 +1,262 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/administrativearea" + "github.com/google/uuid" +) + +// AdministrativeArea is the model entity for the AdministrativeArea schema. +type AdministrativeArea struct { + config `json:"-"` + // ID of the ent. + ID uuid.UUID `json:"id,omitempty"` + // Source holds the value of the "source" field. + Source string `json:"source,omitempty"` + // SourceVersion holds the value of the "source_version" field. + SourceVersion string `json:"source_version,omitempty"` + // ExternalCode holds the value of the "external_code" field. + ExternalCode string `json:"external_code,omitempty"` + // Kind holds the value of the "kind" field. + Kind string `json:"kind,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` + // ParentID holds the value of the "parent_id" field. + ParentID *uuid.UUID `json:"parent_id,omitempty"` + // Active holds the value of the "active" field. + Active bool `json:"active,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the AdministrativeAreaQuery when eager-loading is set. + Edges AdministrativeAreaEdges `json:"edges"` + selectValues sql.SelectValues +} + +// AdministrativeAreaEdges holds the relations/edges for other nodes in the graph. +type AdministrativeAreaEdges struct { + // Children holds the value of the children edge. + Children []*AdministrativeArea `json:"children,omitempty"` + // Parent holds the value of the parent edge. + Parent *AdministrativeArea `json:"parent,omitempty"` + // Localities holds the value of the localities edge. + Localities []*Locality `json:"localities,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [3]bool +} + +// ChildrenOrErr returns the Children value or an error if the edge +// was not loaded in eager-loading. +func (e AdministrativeAreaEdges) ChildrenOrErr() ([]*AdministrativeArea, error) { + if e.loadedTypes[0] { + return e.Children, nil + } + return nil, &NotLoadedError{edge: "children"} +} + +// ParentOrErr returns the Parent value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e AdministrativeAreaEdges) ParentOrErr() (*AdministrativeArea, error) { + if e.Parent != nil { + return e.Parent, nil + } else if e.loadedTypes[1] { + return nil, &NotFoundError{label: administrativearea.Label} + } + return nil, &NotLoadedError{edge: "parent"} +} + +// LocalitiesOrErr returns the Localities value or an error if the edge +// was not loaded in eager-loading. +func (e AdministrativeAreaEdges) LocalitiesOrErr() ([]*Locality, error) { + if e.loadedTypes[2] { + return e.Localities, nil + } + return nil, &NotLoadedError{edge: "localities"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*AdministrativeArea) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case administrativearea.FieldParentID: + values[i] = &sql.NullScanner{S: new(uuid.UUID)} + case administrativearea.FieldActive: + values[i] = new(sql.NullBool) + case administrativearea.FieldSource, administrativearea.FieldSourceVersion, administrativearea.FieldExternalCode, administrativearea.FieldKind, administrativearea.FieldName: + values[i] = new(sql.NullString) + case administrativearea.FieldCreatedAt, administrativearea.FieldUpdatedAt: + values[i] = new(sql.NullTime) + case administrativearea.FieldID: + values[i] = new(uuid.UUID) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the AdministrativeArea fields. +func (_m *AdministrativeArea) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case administrativearea.FieldID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value != nil { + _m.ID = *value + } + case administrativearea.FieldSource: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field source", values[i]) + } else if value.Valid { + _m.Source = value.String + } + case administrativearea.FieldSourceVersion: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field source_version", values[i]) + } else if value.Valid { + _m.SourceVersion = value.String + } + case administrativearea.FieldExternalCode: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field external_code", values[i]) + } else if value.Valid { + _m.ExternalCode = value.String + } + case administrativearea.FieldKind: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field kind", values[i]) + } else if value.Valid { + _m.Kind = value.String + } + case administrativearea.FieldName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field name", values[i]) + } else if value.Valid { + _m.Name = value.String + } + case administrativearea.FieldParentID: + if value, ok := values[i].(*sql.NullScanner); !ok { + return fmt.Errorf("unexpected type %T for field parent_id", values[i]) + } else if value.Valid { + _m.ParentID = new(uuid.UUID) + *_m.ParentID = *value.S.(*uuid.UUID) + } + case administrativearea.FieldActive: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field active", values[i]) + } else if value.Valid { + _m.Active = value.Bool + } + case administrativearea.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + _m.CreatedAt = value.Time + } + case administrativearea.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + _m.UpdatedAt = value.Time + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the AdministrativeArea. +// This includes values selected through modifiers, order, etc. +func (_m *AdministrativeArea) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QueryChildren queries the "children" edge of the AdministrativeArea entity. +func (_m *AdministrativeArea) QueryChildren() *AdministrativeAreaQuery { + return NewAdministrativeAreaClient(_m.config).QueryChildren(_m) +} + +// QueryParent queries the "parent" edge of the AdministrativeArea entity. +func (_m *AdministrativeArea) QueryParent() *AdministrativeAreaQuery { + return NewAdministrativeAreaClient(_m.config).QueryParent(_m) +} + +// QueryLocalities queries the "localities" edge of the AdministrativeArea entity. +func (_m *AdministrativeArea) QueryLocalities() *LocalityQuery { + return NewAdministrativeAreaClient(_m.config).QueryLocalities(_m) +} + +// Update returns a builder for updating this AdministrativeArea. +// Note that you need to call AdministrativeArea.Unwrap() before calling this method if this AdministrativeArea +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *AdministrativeArea) Update() *AdministrativeAreaUpdateOne { + return NewAdministrativeAreaClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the AdministrativeArea entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *AdministrativeArea) Unwrap() *AdministrativeArea { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: AdministrativeArea is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *AdministrativeArea) String() string { + var builder strings.Builder + builder.WriteString("AdministrativeArea(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("source=") + builder.WriteString(_m.Source) + builder.WriteString(", ") + builder.WriteString("source_version=") + builder.WriteString(_m.SourceVersion) + builder.WriteString(", ") + builder.WriteString("external_code=") + builder.WriteString(_m.ExternalCode) + builder.WriteString(", ") + builder.WriteString("kind=") + builder.WriteString(_m.Kind) + builder.WriteString(", ") + builder.WriteString("name=") + builder.WriteString(_m.Name) + builder.WriteString(", ") + if v := _m.ParentID; v != nil { + builder.WriteString("parent_id=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") + builder.WriteString("active=") + builder.WriteString(fmt.Sprintf("%v", _m.Active)) + builder.WriteString(", ") + builder.WriteString("created_at=") + builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(_m.UpdatedAt.Format(time.ANSIC)) + builder.WriteByte(')') + return builder.String() +} + +// AdministrativeAreas is a parsable slice of AdministrativeArea. +type AdministrativeAreas []*AdministrativeArea diff --git a/backend/ent/administrativearea/administrativearea.go b/backend/ent/administrativearea/administrativearea.go new file mode 100644 index 0000000..14afc03 --- /dev/null +++ b/backend/ent/administrativearea/administrativearea.go @@ -0,0 +1,215 @@ +// Code generated by ent, DO NOT EDIT. + +package administrativearea + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/google/uuid" +) + +const ( + // Label holds the string label denoting the administrativearea type in the database. + Label = "administrative_area" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldSource holds the string denoting the source field in the database. + FieldSource = "source" + // FieldSourceVersion holds the string denoting the source_version field in the database. + FieldSourceVersion = "source_version" + // FieldExternalCode holds the string denoting the external_code field in the database. + FieldExternalCode = "external_code" + // FieldKind holds the string denoting the kind field in the database. + FieldKind = "kind" + // FieldName holds the string denoting the name field in the database. + FieldName = "name" + // FieldParentID holds the string denoting the parent_id field in the database. + FieldParentID = "parent_id" + // FieldActive holds the string denoting the active field in the database. + FieldActive = "active" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // EdgeChildren holds the string denoting the children edge name in mutations. + EdgeChildren = "children" + // EdgeParent holds the string denoting the parent edge name in mutations. + EdgeParent = "parent" + // EdgeLocalities holds the string denoting the localities edge name in mutations. + EdgeLocalities = "localities" + // Table holds the table name of the administrativearea in the database. + Table = "administrative_areas" + // ChildrenTable is the table that holds the children relation/edge. + ChildrenTable = "administrative_areas" + // ChildrenColumn is the table column denoting the children relation/edge. + ChildrenColumn = "parent_id" + // ParentTable is the table that holds the parent relation/edge. + ParentTable = "administrative_areas" + // ParentColumn is the table column denoting the parent relation/edge. + ParentColumn = "parent_id" + // LocalitiesTable is the table that holds the localities relation/edge. + LocalitiesTable = "localities" + // LocalitiesInverseTable is the table name for the Locality entity. + // It exists in this package in order to avoid circular dependency with the "locality" package. + LocalitiesInverseTable = "localities" + // LocalitiesColumn is the table column denoting the localities relation/edge. + LocalitiesColumn = "parent_parish_id" +) + +// Columns holds all SQL columns for administrativearea fields. +var Columns = []string{ + FieldID, + FieldSource, + FieldSourceVersion, + FieldExternalCode, + FieldKind, + FieldName, + FieldParentID, + FieldActive, + FieldCreatedAt, + FieldUpdatedAt, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // SourceValidator is a validator for the "source" field. It is called by the builders before save. + SourceValidator func(string) error + // SourceVersionValidator is a validator for the "source_version" field. It is called by the builders before save. + SourceVersionValidator func(string) error + // ExternalCodeValidator is a validator for the "external_code" field. It is called by the builders before save. + ExternalCodeValidator func(string) error + // KindValidator is a validator for the "kind" field. It is called by the builders before save. + KindValidator func(string) error + // NameValidator is a validator for the "name" field. It is called by the builders before save. + NameValidator func(string) error + // DefaultActive holds the default value on creation for the "active" field. + DefaultActive bool + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() uuid.UUID +) + +// OrderOption defines the ordering options for the AdministrativeArea queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// BySource orders the results by the source field. +func BySource(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSource, opts...).ToFunc() +} + +// BySourceVersion orders the results by the source_version field. +func BySourceVersion(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSourceVersion, opts...).ToFunc() +} + +// ByExternalCode orders the results by the external_code field. +func ByExternalCode(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldExternalCode, opts...).ToFunc() +} + +// ByKind orders the results by the kind field. +func ByKind(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldKind, opts...).ToFunc() +} + +// ByName orders the results by the name field. +func ByName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldName, opts...).ToFunc() +} + +// ByParentID orders the results by the parent_id field. +func ByParentID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldParentID, opts...).ToFunc() +} + +// ByActive orders the results by the active field. +func ByActive(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldActive, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByChildrenCount orders the results by children count. +func ByChildrenCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newChildrenStep(), opts...) + } +} + +// ByChildren orders the results by children terms. +func ByChildren(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newChildrenStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByParentField orders the results by parent field. +func ByParentField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newParentStep(), sql.OrderByField(field, opts...)) + } +} + +// ByLocalitiesCount orders the results by localities count. +func ByLocalitiesCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newLocalitiesStep(), opts...) + } +} + +// ByLocalities orders the results by localities terms. +func ByLocalities(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newLocalitiesStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} +func newChildrenStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn), + ) +} +func newParentStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, ParentTable, ParentColumn), + ) +} +func newLocalitiesStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(LocalitiesInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, LocalitiesTable, LocalitiesColumn), + ) +} diff --git a/backend/ent/administrativearea/where.go b/backend/ent/administrativearea/where.go new file mode 100644 index 0000000..d082bc5 --- /dev/null +++ b/backend/ent/administrativearea/where.go @@ -0,0 +1,631 @@ +// Code generated by ent, DO NOT EDIT. + +package administrativearea + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +// ID filters vertices based on their ID field. +func ID(id uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLTE(FieldID, id)) +} + +// Source applies equality check predicate on the "source" field. It's identical to SourceEQ. +func Source(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldSource, v)) +} + +// SourceVersion applies equality check predicate on the "source_version" field. It's identical to SourceVersionEQ. +func SourceVersion(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldSourceVersion, v)) +} + +// ExternalCode applies equality check predicate on the "external_code" field. It's identical to ExternalCodeEQ. +func ExternalCode(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldExternalCode, v)) +} + +// Kind applies equality check predicate on the "kind" field. It's identical to KindEQ. +func Kind(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldKind, v)) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldName, v)) +} + +// ParentID applies equality check predicate on the "parent_id" field. It's identical to ParentIDEQ. +func ParentID(v uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldParentID, v)) +} + +// Active applies equality check predicate on the "active" field. It's identical to ActiveEQ. +func Active(v bool) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldActive, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// SourceEQ applies the EQ predicate on the "source" field. +func SourceEQ(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldSource, v)) +} + +// SourceNEQ applies the NEQ predicate on the "source" field. +func SourceNEQ(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNEQ(FieldSource, v)) +} + +// SourceIn applies the In predicate on the "source" field. +func SourceIn(vs ...string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldIn(FieldSource, vs...)) +} + +// SourceNotIn applies the NotIn predicate on the "source" field. +func SourceNotIn(vs ...string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNotIn(FieldSource, vs...)) +} + +// SourceGT applies the GT predicate on the "source" field. +func SourceGT(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGT(FieldSource, v)) +} + +// SourceGTE applies the GTE predicate on the "source" field. +func SourceGTE(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGTE(FieldSource, v)) +} + +// SourceLT applies the LT predicate on the "source" field. +func SourceLT(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLT(FieldSource, v)) +} + +// SourceLTE applies the LTE predicate on the "source" field. +func SourceLTE(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLTE(FieldSource, v)) +} + +// SourceContains applies the Contains predicate on the "source" field. +func SourceContains(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldContains(FieldSource, v)) +} + +// SourceHasPrefix applies the HasPrefix predicate on the "source" field. +func SourceHasPrefix(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldHasPrefix(FieldSource, v)) +} + +// SourceHasSuffix applies the HasSuffix predicate on the "source" field. +func SourceHasSuffix(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldHasSuffix(FieldSource, v)) +} + +// SourceEqualFold applies the EqualFold predicate on the "source" field. +func SourceEqualFold(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEqualFold(FieldSource, v)) +} + +// SourceContainsFold applies the ContainsFold predicate on the "source" field. +func SourceContainsFold(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldContainsFold(FieldSource, v)) +} + +// SourceVersionEQ applies the EQ predicate on the "source_version" field. +func SourceVersionEQ(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldSourceVersion, v)) +} + +// SourceVersionNEQ applies the NEQ predicate on the "source_version" field. +func SourceVersionNEQ(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNEQ(FieldSourceVersion, v)) +} + +// SourceVersionIn applies the In predicate on the "source_version" field. +func SourceVersionIn(vs ...string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldIn(FieldSourceVersion, vs...)) +} + +// SourceVersionNotIn applies the NotIn predicate on the "source_version" field. +func SourceVersionNotIn(vs ...string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNotIn(FieldSourceVersion, vs...)) +} + +// SourceVersionGT applies the GT predicate on the "source_version" field. +func SourceVersionGT(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGT(FieldSourceVersion, v)) +} + +// SourceVersionGTE applies the GTE predicate on the "source_version" field. +func SourceVersionGTE(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGTE(FieldSourceVersion, v)) +} + +// SourceVersionLT applies the LT predicate on the "source_version" field. +func SourceVersionLT(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLT(FieldSourceVersion, v)) +} + +// SourceVersionLTE applies the LTE predicate on the "source_version" field. +func SourceVersionLTE(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLTE(FieldSourceVersion, v)) +} + +// SourceVersionContains applies the Contains predicate on the "source_version" field. +func SourceVersionContains(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldContains(FieldSourceVersion, v)) +} + +// SourceVersionHasPrefix applies the HasPrefix predicate on the "source_version" field. +func SourceVersionHasPrefix(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldHasPrefix(FieldSourceVersion, v)) +} + +// SourceVersionHasSuffix applies the HasSuffix predicate on the "source_version" field. +func SourceVersionHasSuffix(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldHasSuffix(FieldSourceVersion, v)) +} + +// SourceVersionEqualFold applies the EqualFold predicate on the "source_version" field. +func SourceVersionEqualFold(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEqualFold(FieldSourceVersion, v)) +} + +// SourceVersionContainsFold applies the ContainsFold predicate on the "source_version" field. +func SourceVersionContainsFold(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldContainsFold(FieldSourceVersion, v)) +} + +// ExternalCodeEQ applies the EQ predicate on the "external_code" field. +func ExternalCodeEQ(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldExternalCode, v)) +} + +// ExternalCodeNEQ applies the NEQ predicate on the "external_code" field. +func ExternalCodeNEQ(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNEQ(FieldExternalCode, v)) +} + +// ExternalCodeIn applies the In predicate on the "external_code" field. +func ExternalCodeIn(vs ...string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldIn(FieldExternalCode, vs...)) +} + +// ExternalCodeNotIn applies the NotIn predicate on the "external_code" field. +func ExternalCodeNotIn(vs ...string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNotIn(FieldExternalCode, vs...)) +} + +// ExternalCodeGT applies the GT predicate on the "external_code" field. +func ExternalCodeGT(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGT(FieldExternalCode, v)) +} + +// ExternalCodeGTE applies the GTE predicate on the "external_code" field. +func ExternalCodeGTE(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGTE(FieldExternalCode, v)) +} + +// ExternalCodeLT applies the LT predicate on the "external_code" field. +func ExternalCodeLT(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLT(FieldExternalCode, v)) +} + +// ExternalCodeLTE applies the LTE predicate on the "external_code" field. +func ExternalCodeLTE(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLTE(FieldExternalCode, v)) +} + +// ExternalCodeContains applies the Contains predicate on the "external_code" field. +func ExternalCodeContains(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldContains(FieldExternalCode, v)) +} + +// ExternalCodeHasPrefix applies the HasPrefix predicate on the "external_code" field. +func ExternalCodeHasPrefix(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldHasPrefix(FieldExternalCode, v)) +} + +// ExternalCodeHasSuffix applies the HasSuffix predicate on the "external_code" field. +func ExternalCodeHasSuffix(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldHasSuffix(FieldExternalCode, v)) +} + +// ExternalCodeEqualFold applies the EqualFold predicate on the "external_code" field. +func ExternalCodeEqualFold(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEqualFold(FieldExternalCode, v)) +} + +// ExternalCodeContainsFold applies the ContainsFold predicate on the "external_code" field. +func ExternalCodeContainsFold(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldContainsFold(FieldExternalCode, v)) +} + +// KindEQ applies the EQ predicate on the "kind" field. +func KindEQ(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldKind, v)) +} + +// KindNEQ applies the NEQ predicate on the "kind" field. +func KindNEQ(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNEQ(FieldKind, v)) +} + +// KindIn applies the In predicate on the "kind" field. +func KindIn(vs ...string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldIn(FieldKind, vs...)) +} + +// KindNotIn applies the NotIn predicate on the "kind" field. +func KindNotIn(vs ...string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNotIn(FieldKind, vs...)) +} + +// KindGT applies the GT predicate on the "kind" field. +func KindGT(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGT(FieldKind, v)) +} + +// KindGTE applies the GTE predicate on the "kind" field. +func KindGTE(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGTE(FieldKind, v)) +} + +// KindLT applies the LT predicate on the "kind" field. +func KindLT(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLT(FieldKind, v)) +} + +// KindLTE applies the LTE predicate on the "kind" field. +func KindLTE(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLTE(FieldKind, v)) +} + +// KindContains applies the Contains predicate on the "kind" field. +func KindContains(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldContains(FieldKind, v)) +} + +// KindHasPrefix applies the HasPrefix predicate on the "kind" field. +func KindHasPrefix(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldHasPrefix(FieldKind, v)) +} + +// KindHasSuffix applies the HasSuffix predicate on the "kind" field. +func KindHasSuffix(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldHasSuffix(FieldKind, v)) +} + +// KindEqualFold applies the EqualFold predicate on the "kind" field. +func KindEqualFold(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEqualFold(FieldKind, v)) +} + +// KindContainsFold applies the ContainsFold predicate on the "kind" field. +func KindContainsFold(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldContainsFold(FieldKind, v)) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldName, v)) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNEQ(FieldName, v)) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldIn(FieldName, vs...)) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNotIn(FieldName, vs...)) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGT(FieldName, v)) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGTE(FieldName, v)) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLT(FieldName, v)) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLTE(FieldName, v)) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldContains(FieldName, v)) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldHasPrefix(FieldName, v)) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldHasSuffix(FieldName, v)) +} + +// NameEqualFold applies the EqualFold predicate on the "name" field. +func NameEqualFold(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEqualFold(FieldName, v)) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldContainsFold(FieldName, v)) +} + +// ParentIDEQ applies the EQ predicate on the "parent_id" field. +func ParentIDEQ(v uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldParentID, v)) +} + +// ParentIDNEQ applies the NEQ predicate on the "parent_id" field. +func ParentIDNEQ(v uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNEQ(FieldParentID, v)) +} + +// ParentIDIn applies the In predicate on the "parent_id" field. +func ParentIDIn(vs ...uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldIn(FieldParentID, vs...)) +} + +// ParentIDNotIn applies the NotIn predicate on the "parent_id" field. +func ParentIDNotIn(vs ...uuid.UUID) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNotIn(FieldParentID, vs...)) +} + +// ParentIDIsNil applies the IsNil predicate on the "parent_id" field. +func ParentIDIsNil() predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldIsNull(FieldParentID)) +} + +// ParentIDNotNil applies the NotNil predicate on the "parent_id" field. +func ParentIDNotNil() predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNotNull(FieldParentID)) +} + +// ActiveEQ applies the EQ predicate on the "active" field. +func ActiveEQ(v bool) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldActive, v)) +} + +// ActiveNEQ applies the NEQ predicate on the "active" field. +func ActiveNEQ(v bool) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNEQ(FieldActive, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// HasChildren applies the HasEdge predicate on the "children" edge. +func HasChildren() predicate.AdministrativeArea { + return predicate.AdministrativeArea(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasChildrenWith applies the HasEdge predicate on the "children" edge with a given conditions (other predicates). +func HasChildrenWith(preds ...predicate.AdministrativeArea) predicate.AdministrativeArea { + return predicate.AdministrativeArea(func(s *sql.Selector) { + step := newChildrenStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasParent applies the HasEdge predicate on the "parent" edge. +func HasParent() predicate.AdministrativeArea { + return predicate.AdministrativeArea(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, ParentTable, ParentColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasParentWith applies the HasEdge predicate on the "parent" edge with a given conditions (other predicates). +func HasParentWith(preds ...predicate.AdministrativeArea) predicate.AdministrativeArea { + return predicate.AdministrativeArea(func(s *sql.Selector) { + step := newParentStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasLocalities applies the HasEdge predicate on the "localities" edge. +func HasLocalities() predicate.AdministrativeArea { + return predicate.AdministrativeArea(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, LocalitiesTable, LocalitiesColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasLocalitiesWith applies the HasEdge predicate on the "localities" edge with a given conditions (other predicates). +func HasLocalitiesWith(preds ...predicate.Locality) predicate.AdministrativeArea { + return predicate.AdministrativeArea(func(s *sql.Selector) { + step := newLocalitiesStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.AdministrativeArea) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.AdministrativeArea) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.AdministrativeArea) predicate.AdministrativeArea { + return predicate.AdministrativeArea(sql.NotPredicates(p)) +} diff --git a/backend/ent/administrativearea_create.go b/backend/ent/administrativearea_create.go new file mode 100644 index 0000000..1ed359e --- /dev/null +++ b/backend/ent/administrativearea_create.go @@ -0,0 +1,465 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/administrativearea" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/google/uuid" +) + +// AdministrativeAreaCreate is the builder for creating a AdministrativeArea entity. +type AdministrativeAreaCreate struct { + config + mutation *AdministrativeAreaMutation + hooks []Hook +} + +// SetSource sets the "source" field. +func (_c *AdministrativeAreaCreate) SetSource(v string) *AdministrativeAreaCreate { + _c.mutation.SetSource(v) + return _c +} + +// SetSourceVersion sets the "source_version" field. +func (_c *AdministrativeAreaCreate) SetSourceVersion(v string) *AdministrativeAreaCreate { + _c.mutation.SetSourceVersion(v) + return _c +} + +// SetExternalCode sets the "external_code" field. +func (_c *AdministrativeAreaCreate) SetExternalCode(v string) *AdministrativeAreaCreate { + _c.mutation.SetExternalCode(v) + return _c +} + +// SetKind sets the "kind" field. +func (_c *AdministrativeAreaCreate) SetKind(v string) *AdministrativeAreaCreate { + _c.mutation.SetKind(v) + return _c +} + +// SetName sets the "name" field. +func (_c *AdministrativeAreaCreate) SetName(v string) *AdministrativeAreaCreate { + _c.mutation.SetName(v) + return _c +} + +// SetParentID sets the "parent_id" field. +func (_c *AdministrativeAreaCreate) SetParentID(v uuid.UUID) *AdministrativeAreaCreate { + _c.mutation.SetParentID(v) + return _c +} + +// SetNillableParentID sets the "parent_id" field if the given value is not nil. +func (_c *AdministrativeAreaCreate) SetNillableParentID(v *uuid.UUID) *AdministrativeAreaCreate { + if v != nil { + _c.SetParentID(*v) + } + return _c +} + +// SetActive sets the "active" field. +func (_c *AdministrativeAreaCreate) SetActive(v bool) *AdministrativeAreaCreate { + _c.mutation.SetActive(v) + return _c +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_c *AdministrativeAreaCreate) SetNillableActive(v *bool) *AdministrativeAreaCreate { + if v != nil { + _c.SetActive(*v) + } + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *AdministrativeAreaCreate) SetCreatedAt(v time.Time) *AdministrativeAreaCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *AdministrativeAreaCreate) SetNillableCreatedAt(v *time.Time) *AdministrativeAreaCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *AdministrativeAreaCreate) SetUpdatedAt(v time.Time) *AdministrativeAreaCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (_c *AdministrativeAreaCreate) SetNillableUpdatedAt(v *time.Time) *AdministrativeAreaCreate { + if v != nil { + _c.SetUpdatedAt(*v) + } + return _c +} + +// SetID sets the "id" field. +func (_c *AdministrativeAreaCreate) SetID(v uuid.UUID) *AdministrativeAreaCreate { + _c.mutation.SetID(v) + return _c +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (_c *AdministrativeAreaCreate) SetNillableID(v *uuid.UUID) *AdministrativeAreaCreate { + if v != nil { + _c.SetID(*v) + } + return _c +} + +// AddChildIDs adds the "children" edge to the AdministrativeArea entity by IDs. +func (_c *AdministrativeAreaCreate) AddChildIDs(ids ...uuid.UUID) *AdministrativeAreaCreate { + _c.mutation.AddChildIDs(ids...) + return _c +} + +// AddChildren adds the "children" edges to the AdministrativeArea entity. +func (_c *AdministrativeAreaCreate) AddChildren(v ...*AdministrativeArea) *AdministrativeAreaCreate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddChildIDs(ids...) +} + +// SetParent sets the "parent" edge to the AdministrativeArea entity. +func (_c *AdministrativeAreaCreate) SetParent(v *AdministrativeArea) *AdministrativeAreaCreate { + return _c.SetParentID(v.ID) +} + +// AddLocalityIDs adds the "localities" edge to the Locality entity by IDs. +func (_c *AdministrativeAreaCreate) AddLocalityIDs(ids ...uuid.UUID) *AdministrativeAreaCreate { + _c.mutation.AddLocalityIDs(ids...) + return _c +} + +// AddLocalities adds the "localities" edges to the Locality entity. +func (_c *AdministrativeAreaCreate) AddLocalities(v ...*Locality) *AdministrativeAreaCreate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddLocalityIDs(ids...) +} + +// Mutation returns the AdministrativeAreaMutation object of the builder. +func (_c *AdministrativeAreaCreate) Mutation() *AdministrativeAreaMutation { + return _c.mutation +} + +// Save creates the AdministrativeArea in the database. +func (_c *AdministrativeAreaCreate) Save(ctx context.Context) (*AdministrativeArea, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *AdministrativeAreaCreate) SaveX(ctx context.Context) *AdministrativeArea { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *AdministrativeAreaCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *AdministrativeAreaCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *AdministrativeAreaCreate) defaults() { + if _, ok := _c.mutation.Active(); !ok { + v := administrativearea.DefaultActive + _c.mutation.SetActive(v) + } + if _, ok := _c.mutation.CreatedAt(); !ok { + v := administrativearea.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + v := administrativearea.DefaultUpdatedAt() + _c.mutation.SetUpdatedAt(v) + } + if _, ok := _c.mutation.ID(); !ok { + v := administrativearea.DefaultID() + _c.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *AdministrativeAreaCreate) check() error { + if _, ok := _c.mutation.Source(); !ok { + return &ValidationError{Name: "source", err: errors.New(`ent: missing required field "AdministrativeArea.source"`)} + } + if v, ok := _c.mutation.Source(); ok { + if err := administrativearea.SourceValidator(v); err != nil { + return &ValidationError{Name: "source", err: fmt.Errorf(`ent: validator failed for field "AdministrativeArea.source": %w`, err)} + } + } + if _, ok := _c.mutation.SourceVersion(); !ok { + return &ValidationError{Name: "source_version", err: errors.New(`ent: missing required field "AdministrativeArea.source_version"`)} + } + if v, ok := _c.mutation.SourceVersion(); ok { + if err := administrativearea.SourceVersionValidator(v); err != nil { + return &ValidationError{Name: "source_version", err: fmt.Errorf(`ent: validator failed for field "AdministrativeArea.source_version": %w`, err)} + } + } + if _, ok := _c.mutation.ExternalCode(); !ok { + return &ValidationError{Name: "external_code", err: errors.New(`ent: missing required field "AdministrativeArea.external_code"`)} + } + if v, ok := _c.mutation.ExternalCode(); ok { + if err := administrativearea.ExternalCodeValidator(v); err != nil { + return &ValidationError{Name: "external_code", err: fmt.Errorf(`ent: validator failed for field "AdministrativeArea.external_code": %w`, err)} + } + } + if _, ok := _c.mutation.Kind(); !ok { + return &ValidationError{Name: "kind", err: errors.New(`ent: missing required field "AdministrativeArea.kind"`)} + } + if v, ok := _c.mutation.Kind(); ok { + if err := administrativearea.KindValidator(v); err != nil { + return &ValidationError{Name: "kind", err: fmt.Errorf(`ent: validator failed for field "AdministrativeArea.kind": %w`, err)} + } + } + if _, ok := _c.mutation.Name(); !ok { + return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "AdministrativeArea.name"`)} + } + if v, ok := _c.mutation.Name(); ok { + if err := administrativearea.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "AdministrativeArea.name": %w`, err)} + } + } + if _, ok := _c.mutation.Active(); !ok { + return &ValidationError{Name: "active", err: errors.New(`ent: missing required field "AdministrativeArea.active"`)} + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "AdministrativeArea.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "AdministrativeArea.updated_at"`)} + } + return nil +} + +func (_c *AdministrativeAreaCreate) sqlSave(ctx context.Context) (*AdministrativeArea, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(*uuid.UUID); ok { + _node.ID = *id + } else if err := _node.ID.Scan(_spec.ID.Value); err != nil { + return nil, err + } + } + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *AdministrativeAreaCreate) createSpec() (*AdministrativeArea, *sqlgraph.CreateSpec) { + var ( + _node = &AdministrativeArea{config: _c.config} + _spec = sqlgraph.NewCreateSpec(administrativearea.Table, sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID)) + ) + if id, ok := _c.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = &id + } + if value, ok := _c.mutation.Source(); ok { + _spec.SetField(administrativearea.FieldSource, field.TypeString, value) + _node.Source = value + } + if value, ok := _c.mutation.SourceVersion(); ok { + _spec.SetField(administrativearea.FieldSourceVersion, field.TypeString, value) + _node.SourceVersion = value + } + if value, ok := _c.mutation.ExternalCode(); ok { + _spec.SetField(administrativearea.FieldExternalCode, field.TypeString, value) + _node.ExternalCode = value + } + if value, ok := _c.mutation.Kind(); ok { + _spec.SetField(administrativearea.FieldKind, field.TypeString, value) + _node.Kind = value + } + if value, ok := _c.mutation.Name(); ok { + _spec.SetField(administrativearea.FieldName, field.TypeString, value) + _node.Name = value + } + if value, ok := _c.mutation.Active(); ok { + _spec.SetField(administrativearea.FieldActive, field.TypeBool, value) + _node.Active = value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(administrativearea.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(administrativearea.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if nodes := _c.mutation.ChildrenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.ChildrenTable, + Columns: []string{administrativearea.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.ParentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: administrativearea.ParentTable, + Columns: []string{administrativearea.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.ParentID = &nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.LocalitiesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.LocalitiesTable, + Columns: []string{administrativearea.LocalitiesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// AdministrativeAreaCreateBulk is the builder for creating many AdministrativeArea entities in bulk. +type AdministrativeAreaCreateBulk struct { + config + err error + builders []*AdministrativeAreaCreate +} + +// Save creates the AdministrativeArea entities in the database. +func (_c *AdministrativeAreaCreateBulk) Save(ctx context.Context) ([]*AdministrativeArea, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*AdministrativeArea, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AdministrativeAreaMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *AdministrativeAreaCreateBulk) SaveX(ctx context.Context) []*AdministrativeArea { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *AdministrativeAreaCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *AdministrativeAreaCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/administrativearea_delete.go b/backend/ent/administrativearea_delete.go new file mode 100644 index 0000000..8498266 --- /dev/null +++ b/backend/ent/administrativearea_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/administrativearea" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" +) + +// AdministrativeAreaDelete is the builder for deleting a AdministrativeArea entity. +type AdministrativeAreaDelete struct { + config + hooks []Hook + mutation *AdministrativeAreaMutation +} + +// Where appends a list predicates to the AdministrativeAreaDelete builder. +func (_d *AdministrativeAreaDelete) Where(ps ...predicate.AdministrativeArea) *AdministrativeAreaDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *AdministrativeAreaDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *AdministrativeAreaDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *AdministrativeAreaDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(administrativearea.Table, sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// AdministrativeAreaDeleteOne is the builder for deleting a single AdministrativeArea entity. +type AdministrativeAreaDeleteOne struct { + _d *AdministrativeAreaDelete +} + +// Where appends a list predicates to the AdministrativeAreaDelete builder. +func (_d *AdministrativeAreaDeleteOne) Where(ps ...predicate.AdministrativeArea) *AdministrativeAreaDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *AdministrativeAreaDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{administrativearea.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *AdministrativeAreaDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/administrativearea_query.go b/backend/ent/administrativearea_query.go new file mode 100644 index 0000000..e421048 --- /dev/null +++ b/backend/ent/administrativearea_query.go @@ -0,0 +1,760 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "database/sql/driver" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/administrativearea" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +// AdministrativeAreaQuery is the builder for querying AdministrativeArea entities. +type AdministrativeAreaQuery struct { + config + ctx *QueryContext + order []administrativearea.OrderOption + inters []Interceptor + predicates []predicate.AdministrativeArea + withChildren *AdministrativeAreaQuery + withParent *AdministrativeAreaQuery + withLocalities *LocalityQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the AdministrativeAreaQuery builder. +func (_q *AdministrativeAreaQuery) Where(ps ...predicate.AdministrativeArea) *AdministrativeAreaQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *AdministrativeAreaQuery) Limit(limit int) *AdministrativeAreaQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *AdministrativeAreaQuery) Offset(offset int) *AdministrativeAreaQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *AdministrativeAreaQuery) Unique(unique bool) *AdministrativeAreaQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *AdministrativeAreaQuery) Order(o ...administrativearea.OrderOption) *AdministrativeAreaQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QueryChildren chains the current query on the "children" edge. +func (_q *AdministrativeAreaQuery) QueryChildren() *AdministrativeAreaQuery { + query := (&AdministrativeAreaClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(administrativearea.Table, administrativearea.FieldID, selector), + sqlgraph.To(administrativearea.Table, administrativearea.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, administrativearea.ChildrenTable, administrativearea.ChildrenColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryParent chains the current query on the "parent" edge. +func (_q *AdministrativeAreaQuery) QueryParent() *AdministrativeAreaQuery { + query := (&AdministrativeAreaClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(administrativearea.Table, administrativearea.FieldID, selector), + sqlgraph.To(administrativearea.Table, administrativearea.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, administrativearea.ParentTable, administrativearea.ParentColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryLocalities chains the current query on the "localities" edge. +func (_q *AdministrativeAreaQuery) QueryLocalities() *LocalityQuery { + query := (&LocalityClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(administrativearea.Table, administrativearea.FieldID, selector), + sqlgraph.To(locality.Table, locality.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, administrativearea.LocalitiesTable, administrativearea.LocalitiesColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first AdministrativeArea entity from the query. +// Returns a *NotFoundError when no AdministrativeArea was found. +func (_q *AdministrativeAreaQuery) First(ctx context.Context) (*AdministrativeArea, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{administrativearea.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *AdministrativeAreaQuery) FirstX(ctx context.Context) *AdministrativeArea { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first AdministrativeArea ID from the query. +// Returns a *NotFoundError when no AdministrativeArea ID was found. +func (_q *AdministrativeAreaQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{administrativearea.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *AdministrativeAreaQuery) FirstIDX(ctx context.Context) uuid.UUID { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single AdministrativeArea entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one AdministrativeArea entity is found. +// Returns a *NotFoundError when no AdministrativeArea entities are found. +func (_q *AdministrativeAreaQuery) Only(ctx context.Context) (*AdministrativeArea, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{administrativearea.Label} + default: + return nil, &NotSingularError{administrativearea.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *AdministrativeAreaQuery) OnlyX(ctx context.Context) *AdministrativeArea { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only AdministrativeArea ID in the query. +// Returns a *NotSingularError when more than one AdministrativeArea ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *AdministrativeAreaQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{administrativearea.Label} + default: + err = &NotSingularError{administrativearea.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *AdministrativeAreaQuery) OnlyIDX(ctx context.Context) uuid.UUID { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of AdministrativeAreas. +func (_q *AdministrativeAreaQuery) All(ctx context.Context) ([]*AdministrativeArea, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*AdministrativeArea, *AdministrativeAreaQuery]() + return withInterceptors[[]*AdministrativeArea](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *AdministrativeAreaQuery) AllX(ctx context.Context) []*AdministrativeArea { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of AdministrativeArea IDs. +func (_q *AdministrativeAreaQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(administrativearea.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *AdministrativeAreaQuery) IDsX(ctx context.Context) []uuid.UUID { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *AdministrativeAreaQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*AdministrativeAreaQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *AdministrativeAreaQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *AdministrativeAreaQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *AdministrativeAreaQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the AdministrativeAreaQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *AdministrativeAreaQuery) Clone() *AdministrativeAreaQuery { + if _q == nil { + return nil + } + return &AdministrativeAreaQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]administrativearea.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.AdministrativeArea{}, _q.predicates...), + withChildren: _q.withChildren.Clone(), + withParent: _q.withParent.Clone(), + withLocalities: _q.withLocalities.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// WithChildren tells the query-builder to eager-load the nodes that are connected to +// the "children" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *AdministrativeAreaQuery) WithChildren(opts ...func(*AdministrativeAreaQuery)) *AdministrativeAreaQuery { + query := (&AdministrativeAreaClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withChildren = query + return _q +} + +// WithParent tells the query-builder to eager-load the nodes that are connected to +// the "parent" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *AdministrativeAreaQuery) WithParent(opts ...func(*AdministrativeAreaQuery)) *AdministrativeAreaQuery { + query := (&AdministrativeAreaClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withParent = query + return _q +} + +// WithLocalities tells the query-builder to eager-load the nodes that are connected to +// the "localities" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *AdministrativeAreaQuery) WithLocalities(opts ...func(*LocalityQuery)) *AdministrativeAreaQuery { + query := (&LocalityClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withLocalities = query + return _q +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// Source string `json:"source,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.AdministrativeArea.Query(). +// GroupBy(administrativearea.FieldSource). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *AdministrativeAreaQuery) GroupBy(field string, fields ...string) *AdministrativeAreaGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &AdministrativeAreaGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = administrativearea.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// Source string `json:"source,omitempty"` +// } +// +// client.AdministrativeArea.Query(). +// Select(administrativearea.FieldSource). +// Scan(ctx, &v) +func (_q *AdministrativeAreaQuery) Select(fields ...string) *AdministrativeAreaSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &AdministrativeAreaSelect{AdministrativeAreaQuery: _q} + sbuild.label = administrativearea.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a AdministrativeAreaSelect configured with the given aggregations. +func (_q *AdministrativeAreaQuery) Aggregate(fns ...AggregateFunc) *AdministrativeAreaSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *AdministrativeAreaQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !administrativearea.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *AdministrativeAreaQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AdministrativeArea, error) { + var ( + nodes = []*AdministrativeArea{} + _spec = _q.querySpec() + loadedTypes = [3]bool{ + _q.withChildren != nil, + _q.withParent != nil, + _q.withLocalities != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*AdministrativeArea).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &AdministrativeArea{config: _q.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := _q.withChildren; query != nil { + if err := _q.loadChildren(ctx, query, nodes, + func(n *AdministrativeArea) { n.Edges.Children = []*AdministrativeArea{} }, + func(n *AdministrativeArea, e *AdministrativeArea) { n.Edges.Children = append(n.Edges.Children, e) }); err != nil { + return nil, err + } + } + if query := _q.withParent; query != nil { + if err := _q.loadParent(ctx, query, nodes, nil, + func(n *AdministrativeArea, e *AdministrativeArea) { n.Edges.Parent = e }); err != nil { + return nil, err + } + } + if query := _q.withLocalities; query != nil { + if err := _q.loadLocalities(ctx, query, nodes, + func(n *AdministrativeArea) { n.Edges.Localities = []*Locality{} }, + func(n *AdministrativeArea, e *Locality) { n.Edges.Localities = append(n.Edges.Localities, e) }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *AdministrativeAreaQuery) loadChildren(ctx context.Context, query *AdministrativeAreaQuery, nodes []*AdministrativeArea, init func(*AdministrativeArea), assign func(*AdministrativeArea, *AdministrativeArea)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[uuid.UUID]*AdministrativeArea) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(administrativearea.FieldParentID) + } + query.Where(predicate.AdministrativeArea(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(administrativearea.ChildrenColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.ParentID + if fk == nil { + return fmt.Errorf(`foreign-key "parent_id" is nil for node %v`, n.ID) + } + node, ok := nodeids[*fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "parent_id" returned %v for node %v`, *fk, n.ID) + } + assign(node, n) + } + return nil +} +func (_q *AdministrativeAreaQuery) loadParent(ctx context.Context, query *AdministrativeAreaQuery, nodes []*AdministrativeArea, init func(*AdministrativeArea), assign func(*AdministrativeArea, *AdministrativeArea)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*AdministrativeArea) + for i := range nodes { + if nodes[i].ParentID == nil { + continue + } + fk := *nodes[i].ParentID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(administrativearea.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "parent_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (_q *AdministrativeAreaQuery) loadLocalities(ctx context.Context, query *LocalityQuery, nodes []*AdministrativeArea, init func(*AdministrativeArea), assign func(*AdministrativeArea, *Locality)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[uuid.UUID]*AdministrativeArea) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(locality.FieldParentParishID) + } + query.Where(predicate.Locality(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(administrativearea.LocalitiesColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.ParentParishID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "parent_parish_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} + +func (_q *AdministrativeAreaQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *AdministrativeAreaQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(administrativearea.Table, administrativearea.Columns, sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, administrativearea.FieldID) + for i := range fields { + if fields[i] != administrativearea.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if _q.withParent != nil { + _spec.Node.AddColumnOnce(administrativearea.FieldParentID) + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *AdministrativeAreaQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(administrativearea.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = administrativearea.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// AdministrativeAreaGroupBy is the group-by builder for AdministrativeArea entities. +type AdministrativeAreaGroupBy struct { + selector + build *AdministrativeAreaQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *AdministrativeAreaGroupBy) Aggregate(fns ...AggregateFunc) *AdministrativeAreaGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *AdministrativeAreaGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AdministrativeAreaQuery, *AdministrativeAreaGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *AdministrativeAreaGroupBy) sqlScan(ctx context.Context, root *AdministrativeAreaQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// AdministrativeAreaSelect is the builder for selecting fields of AdministrativeArea entities. +type AdministrativeAreaSelect struct { + *AdministrativeAreaQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *AdministrativeAreaSelect) Aggregate(fns ...AggregateFunc) *AdministrativeAreaSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *AdministrativeAreaSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*AdministrativeAreaQuery, *AdministrativeAreaSelect](ctx, _s.AdministrativeAreaQuery, _s, _s.inters, v) +} + +func (_s *AdministrativeAreaSelect) sqlScan(ctx context.Context, root *AdministrativeAreaQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/administrativearea_update.go b/backend/ent/administrativearea_update.go new file mode 100644 index 0000000..982b80f --- /dev/null +++ b/backend/ent/administrativearea_update.go @@ -0,0 +1,632 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/administrativearea" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +// AdministrativeAreaUpdate is the builder for updating AdministrativeArea entities. +type AdministrativeAreaUpdate struct { + config + hooks []Hook + mutation *AdministrativeAreaMutation +} + +// Where appends a list predicates to the AdministrativeAreaUpdate builder. +func (_u *AdministrativeAreaUpdate) Where(ps ...predicate.AdministrativeArea) *AdministrativeAreaUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetName sets the "name" field. +func (_u *AdministrativeAreaUpdate) SetName(v string) *AdministrativeAreaUpdate { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *AdministrativeAreaUpdate) SetNillableName(v *string) *AdministrativeAreaUpdate { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetActive sets the "active" field. +func (_u *AdministrativeAreaUpdate) SetActive(v bool) *AdministrativeAreaUpdate { + _u.mutation.SetActive(v) + return _u +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_u *AdministrativeAreaUpdate) SetNillableActive(v *bool) *AdministrativeAreaUpdate { + if v != nil { + _u.SetActive(*v) + } + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *AdministrativeAreaUpdate) SetUpdatedAt(v time.Time) *AdministrativeAreaUpdate { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// AddChildIDs adds the "children" edge to the AdministrativeArea entity by IDs. +func (_u *AdministrativeAreaUpdate) AddChildIDs(ids ...uuid.UUID) *AdministrativeAreaUpdate { + _u.mutation.AddChildIDs(ids...) + return _u +} + +// AddChildren adds the "children" edges to the AdministrativeArea entity. +func (_u *AdministrativeAreaUpdate) AddChildren(v ...*AdministrativeArea) *AdministrativeAreaUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddChildIDs(ids...) +} + +// AddLocalityIDs adds the "localities" edge to the Locality entity by IDs. +func (_u *AdministrativeAreaUpdate) AddLocalityIDs(ids ...uuid.UUID) *AdministrativeAreaUpdate { + _u.mutation.AddLocalityIDs(ids...) + return _u +} + +// AddLocalities adds the "localities" edges to the Locality entity. +func (_u *AdministrativeAreaUpdate) AddLocalities(v ...*Locality) *AdministrativeAreaUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddLocalityIDs(ids...) +} + +// Mutation returns the AdministrativeAreaMutation object of the builder. +func (_u *AdministrativeAreaUpdate) Mutation() *AdministrativeAreaMutation { + return _u.mutation +} + +// ClearChildren clears all "children" edges to the AdministrativeArea entity. +func (_u *AdministrativeAreaUpdate) ClearChildren() *AdministrativeAreaUpdate { + _u.mutation.ClearChildren() + return _u +} + +// RemoveChildIDs removes the "children" edge to AdministrativeArea entities by IDs. +func (_u *AdministrativeAreaUpdate) RemoveChildIDs(ids ...uuid.UUID) *AdministrativeAreaUpdate { + _u.mutation.RemoveChildIDs(ids...) + return _u +} + +// RemoveChildren removes "children" edges to AdministrativeArea entities. +func (_u *AdministrativeAreaUpdate) RemoveChildren(v ...*AdministrativeArea) *AdministrativeAreaUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveChildIDs(ids...) +} + +// ClearLocalities clears all "localities" edges to the Locality entity. +func (_u *AdministrativeAreaUpdate) ClearLocalities() *AdministrativeAreaUpdate { + _u.mutation.ClearLocalities() + return _u +} + +// RemoveLocalityIDs removes the "localities" edge to Locality entities by IDs. +func (_u *AdministrativeAreaUpdate) RemoveLocalityIDs(ids ...uuid.UUID) *AdministrativeAreaUpdate { + _u.mutation.RemoveLocalityIDs(ids...) + return _u +} + +// RemoveLocalities removes "localities" edges to Locality entities. +func (_u *AdministrativeAreaUpdate) RemoveLocalities(v ...*Locality) *AdministrativeAreaUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveLocalityIDs(ids...) +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *AdministrativeAreaUpdate) Save(ctx context.Context) (int, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *AdministrativeAreaUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *AdministrativeAreaUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *AdministrativeAreaUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *AdministrativeAreaUpdate) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := administrativearea.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *AdministrativeAreaUpdate) check() error { + if v, ok := _u.mutation.Name(); ok { + if err := administrativearea.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "AdministrativeArea.name": %w`, err)} + } + } + return nil +} + +func (_u *AdministrativeAreaUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(administrativearea.Table, administrativearea.Columns, sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(administrativearea.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Active(); ok { + _spec.SetField(administrativearea.FieldActive, field.TypeBool, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(administrativearea.FieldUpdatedAt, field.TypeTime, value) + } + if _u.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.ChildrenTable, + Columns: []string{administrativearea.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedChildrenIDs(); len(nodes) > 0 && !_u.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.ChildrenTable, + Columns: []string{administrativearea.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.ChildrenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.ChildrenTable, + Columns: []string{administrativearea.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _u.mutation.LocalitiesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.LocalitiesTable, + Columns: []string{administrativearea.LocalitiesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedLocalitiesIDs(); len(nodes) > 0 && !_u.mutation.LocalitiesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.LocalitiesTable, + Columns: []string{administrativearea.LocalitiesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.LocalitiesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.LocalitiesTable, + Columns: []string{administrativearea.LocalitiesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{administrativearea.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// AdministrativeAreaUpdateOne is the builder for updating a single AdministrativeArea entity. +type AdministrativeAreaUpdateOne struct { + config + fields []string + hooks []Hook + mutation *AdministrativeAreaMutation +} + +// SetName sets the "name" field. +func (_u *AdministrativeAreaUpdateOne) SetName(v string) *AdministrativeAreaUpdateOne { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *AdministrativeAreaUpdateOne) SetNillableName(v *string) *AdministrativeAreaUpdateOne { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetActive sets the "active" field. +func (_u *AdministrativeAreaUpdateOne) SetActive(v bool) *AdministrativeAreaUpdateOne { + _u.mutation.SetActive(v) + return _u +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_u *AdministrativeAreaUpdateOne) SetNillableActive(v *bool) *AdministrativeAreaUpdateOne { + if v != nil { + _u.SetActive(*v) + } + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *AdministrativeAreaUpdateOne) SetUpdatedAt(v time.Time) *AdministrativeAreaUpdateOne { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// AddChildIDs adds the "children" edge to the AdministrativeArea entity by IDs. +func (_u *AdministrativeAreaUpdateOne) AddChildIDs(ids ...uuid.UUID) *AdministrativeAreaUpdateOne { + _u.mutation.AddChildIDs(ids...) + return _u +} + +// AddChildren adds the "children" edges to the AdministrativeArea entity. +func (_u *AdministrativeAreaUpdateOne) AddChildren(v ...*AdministrativeArea) *AdministrativeAreaUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddChildIDs(ids...) +} + +// AddLocalityIDs adds the "localities" edge to the Locality entity by IDs. +func (_u *AdministrativeAreaUpdateOne) AddLocalityIDs(ids ...uuid.UUID) *AdministrativeAreaUpdateOne { + _u.mutation.AddLocalityIDs(ids...) + return _u +} + +// AddLocalities adds the "localities" edges to the Locality entity. +func (_u *AdministrativeAreaUpdateOne) AddLocalities(v ...*Locality) *AdministrativeAreaUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddLocalityIDs(ids...) +} + +// Mutation returns the AdministrativeAreaMutation object of the builder. +func (_u *AdministrativeAreaUpdateOne) Mutation() *AdministrativeAreaMutation { + return _u.mutation +} + +// ClearChildren clears all "children" edges to the AdministrativeArea entity. +func (_u *AdministrativeAreaUpdateOne) ClearChildren() *AdministrativeAreaUpdateOne { + _u.mutation.ClearChildren() + return _u +} + +// RemoveChildIDs removes the "children" edge to AdministrativeArea entities by IDs. +func (_u *AdministrativeAreaUpdateOne) RemoveChildIDs(ids ...uuid.UUID) *AdministrativeAreaUpdateOne { + _u.mutation.RemoveChildIDs(ids...) + return _u +} + +// RemoveChildren removes "children" edges to AdministrativeArea entities. +func (_u *AdministrativeAreaUpdateOne) RemoveChildren(v ...*AdministrativeArea) *AdministrativeAreaUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveChildIDs(ids...) +} + +// ClearLocalities clears all "localities" edges to the Locality entity. +func (_u *AdministrativeAreaUpdateOne) ClearLocalities() *AdministrativeAreaUpdateOne { + _u.mutation.ClearLocalities() + return _u +} + +// RemoveLocalityIDs removes the "localities" edge to Locality entities by IDs. +func (_u *AdministrativeAreaUpdateOne) RemoveLocalityIDs(ids ...uuid.UUID) *AdministrativeAreaUpdateOne { + _u.mutation.RemoveLocalityIDs(ids...) + return _u +} + +// RemoveLocalities removes "localities" edges to Locality entities. +func (_u *AdministrativeAreaUpdateOne) RemoveLocalities(v ...*Locality) *AdministrativeAreaUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveLocalityIDs(ids...) +} + +// Where appends a list predicates to the AdministrativeAreaUpdate builder. +func (_u *AdministrativeAreaUpdateOne) Where(ps ...predicate.AdministrativeArea) *AdministrativeAreaUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *AdministrativeAreaUpdateOne) Select(field string, fields ...string) *AdministrativeAreaUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated AdministrativeArea entity. +func (_u *AdministrativeAreaUpdateOne) Save(ctx context.Context) (*AdministrativeArea, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *AdministrativeAreaUpdateOne) SaveX(ctx context.Context) *AdministrativeArea { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *AdministrativeAreaUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *AdministrativeAreaUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *AdministrativeAreaUpdateOne) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := administrativearea.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *AdministrativeAreaUpdateOne) check() error { + if v, ok := _u.mutation.Name(); ok { + if err := administrativearea.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "AdministrativeArea.name": %w`, err)} + } + } + return nil +} + +func (_u *AdministrativeAreaUpdateOne) sqlSave(ctx context.Context) (_node *AdministrativeArea, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(administrativearea.Table, administrativearea.Columns, sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "AdministrativeArea.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, administrativearea.FieldID) + for _, f := range fields { + if !administrativearea.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != administrativearea.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(administrativearea.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Active(); ok { + _spec.SetField(administrativearea.FieldActive, field.TypeBool, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(administrativearea.FieldUpdatedAt, field.TypeTime, value) + } + if _u.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.ChildrenTable, + Columns: []string{administrativearea.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedChildrenIDs(); len(nodes) > 0 && !_u.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.ChildrenTable, + Columns: []string{administrativearea.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.ChildrenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.ChildrenTable, + Columns: []string{administrativearea.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _u.mutation.LocalitiesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.LocalitiesTable, + Columns: []string{administrativearea.LocalitiesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedLocalitiesIDs(); len(nodes) > 0 && !_u.mutation.LocalitiesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.LocalitiesTable, + Columns: []string{administrativearea.LocalitiesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.LocalitiesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: administrativearea.LocalitiesTable, + Columns: []string{administrativearea.LocalitiesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &AdministrativeArea{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{administrativearea.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/ent/client.go b/backend/ent/client.go index 58fc681..38cad9a 100644 --- a/backend/ent/client.go +++ b/backend/ent/client.go @@ -15,7 +15,18 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect" "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/administrativearea" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerservicelocality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerspokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategorytranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguagetranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" ) @@ -24,8 +35,28 @@ type Client struct { config // Schema is the client for creating, migrating and dropping schema. Schema *migrate.Schema + // AdministrativeArea is the client for interacting with the AdministrativeArea builders. + AdministrativeArea *AdministrativeAreaClient // InternalUser is the client for interacting with the InternalUser builders. InternalUser *InternalUserClient + // Locality is the client for interacting with the Locality builders. + Locality *LocalityClient + // ProviderProfile is the client for interacting with the ProviderProfile builders. + ProviderProfile *ProviderProfileClient + // ProviderServiceLocality is the client for interacting with the ProviderServiceLocality builders. + ProviderServiceLocality *ProviderServiceLocalityClient + // ProviderSpokenLanguage is the client for interacting with the ProviderSpokenLanguage builders. + ProviderSpokenLanguage *ProviderSpokenLanguageClient + // ServiceCategory is the client for interacting with the ServiceCategory builders. + ServiceCategory *ServiceCategoryClient + // ServiceCategoryTranslation is the client for interacting with the ServiceCategoryTranslation builders. + ServiceCategoryTranslation *ServiceCategoryTranslationClient + // SpokenLanguage is the client for interacting with the SpokenLanguage builders. + SpokenLanguage *SpokenLanguageClient + // SpokenLanguageTranslation is the client for interacting with the SpokenLanguageTranslation builders. + SpokenLanguageTranslation *SpokenLanguageTranslationClient + // SupportedLocale is the client for interacting with the SupportedLocale builders. + SupportedLocale *SupportedLocaleClient // UserAccount is the client for interacting with the UserAccount builders. UserAccount *UserAccountClient } @@ -39,7 +70,17 @@ func NewClient(opts ...Option) *Client { func (c *Client) init() { c.Schema = migrate.NewSchema(c.driver) + c.AdministrativeArea = NewAdministrativeAreaClient(c.config) c.InternalUser = NewInternalUserClient(c.config) + c.Locality = NewLocalityClient(c.config) + c.ProviderProfile = NewProviderProfileClient(c.config) + c.ProviderServiceLocality = NewProviderServiceLocalityClient(c.config) + c.ProviderSpokenLanguage = NewProviderSpokenLanguageClient(c.config) + c.ServiceCategory = NewServiceCategoryClient(c.config) + c.ServiceCategoryTranslation = NewServiceCategoryTranslationClient(c.config) + c.SpokenLanguage = NewSpokenLanguageClient(c.config) + c.SpokenLanguageTranslation = NewSpokenLanguageTranslationClient(c.config) + c.SupportedLocale = NewSupportedLocaleClient(c.config) c.UserAccount = NewUserAccountClient(c.config) } @@ -131,10 +172,20 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { cfg := c.config cfg.driver = tx return &Tx{ - ctx: ctx, - config: cfg, - InternalUser: NewInternalUserClient(cfg), - UserAccount: NewUserAccountClient(cfg), + ctx: ctx, + config: cfg, + AdministrativeArea: NewAdministrativeAreaClient(cfg), + InternalUser: NewInternalUserClient(cfg), + Locality: NewLocalityClient(cfg), + ProviderProfile: NewProviderProfileClient(cfg), + ProviderServiceLocality: NewProviderServiceLocalityClient(cfg), + ProviderSpokenLanguage: NewProviderSpokenLanguageClient(cfg), + ServiceCategory: NewServiceCategoryClient(cfg), + ServiceCategoryTranslation: NewServiceCategoryTranslationClient(cfg), + SpokenLanguage: NewSpokenLanguageClient(cfg), + SpokenLanguageTranslation: NewSpokenLanguageTranslationClient(cfg), + SupportedLocale: NewSupportedLocaleClient(cfg), + UserAccount: NewUserAccountClient(cfg), }, nil } @@ -152,17 +203,27 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) cfg := c.config cfg.driver = &txDriver{tx: tx, drv: c.driver} return &Tx{ - ctx: ctx, - config: cfg, - InternalUser: NewInternalUserClient(cfg), - UserAccount: NewUserAccountClient(cfg), + ctx: ctx, + config: cfg, + AdministrativeArea: NewAdministrativeAreaClient(cfg), + InternalUser: NewInternalUserClient(cfg), + Locality: NewLocalityClient(cfg), + ProviderProfile: NewProviderProfileClient(cfg), + ProviderServiceLocality: NewProviderServiceLocalityClient(cfg), + ProviderSpokenLanguage: NewProviderSpokenLanguageClient(cfg), + ServiceCategory: NewServiceCategoryClient(cfg), + ServiceCategoryTranslation: NewServiceCategoryTranslationClient(cfg), + SpokenLanguage: NewSpokenLanguageClient(cfg), + SpokenLanguageTranslation: NewSpokenLanguageTranslationClient(cfg), + SupportedLocale: NewSupportedLocaleClient(cfg), + UserAccount: NewUserAccountClient(cfg), }, nil } // Debug returns a new debug-client. It's used to get verbose logging on specific operations. // // client.Debug(). -// InternalUser. +// AdministrativeArea. // Query(). // Count(ctx) func (c *Client) Debug() *Client { @@ -184,22 +245,54 @@ func (c *Client) Close() error { // Use adds the mutation hooks to all the entity clients. // In order to add hooks to a specific client, call: `client.Node.Use(...)`. func (c *Client) Use(hooks ...Hook) { - c.InternalUser.Use(hooks...) - c.UserAccount.Use(hooks...) + for _, n := range []interface{ Use(...Hook) }{ + c.AdministrativeArea, c.InternalUser, c.Locality, c.ProviderProfile, + c.ProviderServiceLocality, c.ProviderSpokenLanguage, c.ServiceCategory, + c.ServiceCategoryTranslation, c.SpokenLanguage, c.SpokenLanguageTranslation, + c.SupportedLocale, c.UserAccount, + } { + n.Use(hooks...) + } } // Intercept adds the query interceptors to all the entity clients. // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. func (c *Client) Intercept(interceptors ...Interceptor) { - c.InternalUser.Intercept(interceptors...) - c.UserAccount.Intercept(interceptors...) + for _, n := range []interface{ Intercept(...Interceptor) }{ + c.AdministrativeArea, c.InternalUser, c.Locality, c.ProviderProfile, + c.ProviderServiceLocality, c.ProviderSpokenLanguage, c.ServiceCategory, + c.ServiceCategoryTranslation, c.SpokenLanguage, c.SpokenLanguageTranslation, + c.SupportedLocale, c.UserAccount, + } { + n.Intercept(interceptors...) + } } // Mutate implements the ent.Mutator interface. func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { switch m := m.(type) { + case *AdministrativeAreaMutation: + return c.AdministrativeArea.mutate(ctx, m) case *InternalUserMutation: return c.InternalUser.mutate(ctx, m) + case *LocalityMutation: + return c.Locality.mutate(ctx, m) + case *ProviderProfileMutation: + return c.ProviderProfile.mutate(ctx, m) + case *ProviderServiceLocalityMutation: + return c.ProviderServiceLocality.mutate(ctx, m) + case *ProviderSpokenLanguageMutation: + return c.ProviderSpokenLanguage.mutate(ctx, m) + case *ServiceCategoryMutation: + return c.ServiceCategory.mutate(ctx, m) + case *ServiceCategoryTranslationMutation: + return c.ServiceCategoryTranslation.mutate(ctx, m) + case *SpokenLanguageMutation: + return c.SpokenLanguage.mutate(ctx, m) + case *SpokenLanguageTranslationMutation: + return c.SpokenLanguageTranslation.mutate(ctx, m) + case *SupportedLocaleMutation: + return c.SupportedLocale.mutate(ctx, m) case *UserAccountMutation: return c.UserAccount.mutate(ctx, m) default: @@ -207,6 +300,187 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { } } +// AdministrativeAreaClient is a client for the AdministrativeArea schema. +type AdministrativeAreaClient struct { + config +} + +// NewAdministrativeAreaClient returns a client for the AdministrativeArea from the given config. +func NewAdministrativeAreaClient(c config) *AdministrativeAreaClient { + return &AdministrativeAreaClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `administrativearea.Hooks(f(g(h())))`. +func (c *AdministrativeAreaClient) Use(hooks ...Hook) { + c.hooks.AdministrativeArea = append(c.hooks.AdministrativeArea, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `administrativearea.Intercept(f(g(h())))`. +func (c *AdministrativeAreaClient) Intercept(interceptors ...Interceptor) { + c.inters.AdministrativeArea = append(c.inters.AdministrativeArea, interceptors...) +} + +// Create returns a builder for creating a AdministrativeArea entity. +func (c *AdministrativeAreaClient) Create() *AdministrativeAreaCreate { + mutation := newAdministrativeAreaMutation(c.config, OpCreate) + return &AdministrativeAreaCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of AdministrativeArea entities. +func (c *AdministrativeAreaClient) CreateBulk(builders ...*AdministrativeAreaCreate) *AdministrativeAreaCreateBulk { + return &AdministrativeAreaCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *AdministrativeAreaClient) MapCreateBulk(slice any, setFunc func(*AdministrativeAreaCreate, int)) *AdministrativeAreaCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &AdministrativeAreaCreateBulk{err: fmt.Errorf("calling to AdministrativeAreaClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*AdministrativeAreaCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &AdministrativeAreaCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for AdministrativeArea. +func (c *AdministrativeAreaClient) Update() *AdministrativeAreaUpdate { + mutation := newAdministrativeAreaMutation(c.config, OpUpdate) + return &AdministrativeAreaUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *AdministrativeAreaClient) UpdateOne(_m *AdministrativeArea) *AdministrativeAreaUpdateOne { + mutation := newAdministrativeAreaMutation(c.config, OpUpdateOne, withAdministrativeArea(_m)) + return &AdministrativeAreaUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *AdministrativeAreaClient) UpdateOneID(id uuid.UUID) *AdministrativeAreaUpdateOne { + mutation := newAdministrativeAreaMutation(c.config, OpUpdateOne, withAdministrativeAreaID(id)) + return &AdministrativeAreaUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for AdministrativeArea. +func (c *AdministrativeAreaClient) Delete() *AdministrativeAreaDelete { + mutation := newAdministrativeAreaMutation(c.config, OpDelete) + return &AdministrativeAreaDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *AdministrativeAreaClient) DeleteOne(_m *AdministrativeArea) *AdministrativeAreaDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *AdministrativeAreaClient) DeleteOneID(id uuid.UUID) *AdministrativeAreaDeleteOne { + builder := c.Delete().Where(administrativearea.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &AdministrativeAreaDeleteOne{builder} +} + +// Query returns a query builder for AdministrativeArea. +func (c *AdministrativeAreaClient) Query() *AdministrativeAreaQuery { + return &AdministrativeAreaQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeAdministrativeArea}, + inters: c.Interceptors(), + } +} + +// Get returns a AdministrativeArea entity by its id. +func (c *AdministrativeAreaClient) Get(ctx context.Context, id uuid.UUID) (*AdministrativeArea, error) { + return c.Query().Where(administrativearea.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *AdministrativeAreaClient) GetX(ctx context.Context, id uuid.UUID) *AdministrativeArea { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryChildren queries the children edge of a AdministrativeArea. +func (c *AdministrativeAreaClient) QueryChildren(_m *AdministrativeArea) *AdministrativeAreaQuery { + query := (&AdministrativeAreaClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(administrativearea.Table, administrativearea.FieldID, id), + sqlgraph.To(administrativearea.Table, administrativearea.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, administrativearea.ChildrenTable, administrativearea.ChildrenColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryParent queries the parent edge of a AdministrativeArea. +func (c *AdministrativeAreaClient) QueryParent(_m *AdministrativeArea) *AdministrativeAreaQuery { + query := (&AdministrativeAreaClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(administrativearea.Table, administrativearea.FieldID, id), + sqlgraph.To(administrativearea.Table, administrativearea.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, administrativearea.ParentTable, administrativearea.ParentColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryLocalities queries the localities edge of a AdministrativeArea. +func (c *AdministrativeAreaClient) QueryLocalities(_m *AdministrativeArea) *LocalityQuery { + query := (&LocalityClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(administrativearea.Table, administrativearea.FieldID, id), + sqlgraph.To(locality.Table, locality.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, administrativearea.LocalitiesTable, administrativearea.LocalitiesColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *AdministrativeAreaClient) Hooks() []Hook { + return c.hooks.AdministrativeArea +} + +// Interceptors returns the client interceptors. +func (c *AdministrativeAreaClient) Interceptors() []Interceptor { + return c.inters.AdministrativeArea +} + +func (c *AdministrativeAreaClient) mutate(ctx context.Context, m *AdministrativeAreaMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&AdministrativeAreaCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&AdministrativeAreaUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&AdministrativeAreaUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&AdministrativeAreaDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown AdministrativeArea mutation op: %q", m.Op()) + } +} + // InternalUserClient is a client for the InternalUser schema. type InternalUserClient struct { config @@ -340,6 +614,1375 @@ func (c *InternalUserClient) mutate(ctx context.Context, m *InternalUserMutation } } +// LocalityClient is a client for the Locality schema. +type LocalityClient struct { + config +} + +// NewLocalityClient returns a client for the Locality from the given config. +func NewLocalityClient(c config) *LocalityClient { + return &LocalityClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `locality.Hooks(f(g(h())))`. +func (c *LocalityClient) Use(hooks ...Hook) { + c.hooks.Locality = append(c.hooks.Locality, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `locality.Intercept(f(g(h())))`. +func (c *LocalityClient) Intercept(interceptors ...Interceptor) { + c.inters.Locality = append(c.inters.Locality, interceptors...) +} + +// Create returns a builder for creating a Locality entity. +func (c *LocalityClient) Create() *LocalityCreate { + mutation := newLocalityMutation(c.config, OpCreate) + return &LocalityCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of Locality entities. +func (c *LocalityClient) CreateBulk(builders ...*LocalityCreate) *LocalityCreateBulk { + return &LocalityCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *LocalityClient) MapCreateBulk(slice any, setFunc func(*LocalityCreate, int)) *LocalityCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &LocalityCreateBulk{err: fmt.Errorf("calling to LocalityClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*LocalityCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &LocalityCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for Locality. +func (c *LocalityClient) Update() *LocalityUpdate { + mutation := newLocalityMutation(c.config, OpUpdate) + return &LocalityUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *LocalityClient) UpdateOne(_m *Locality) *LocalityUpdateOne { + mutation := newLocalityMutation(c.config, OpUpdateOne, withLocality(_m)) + return &LocalityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *LocalityClient) UpdateOneID(id uuid.UUID) *LocalityUpdateOne { + mutation := newLocalityMutation(c.config, OpUpdateOne, withLocalityID(id)) + return &LocalityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for Locality. +func (c *LocalityClient) Delete() *LocalityDelete { + mutation := newLocalityMutation(c.config, OpDelete) + return &LocalityDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *LocalityClient) DeleteOne(_m *Locality) *LocalityDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *LocalityClient) DeleteOneID(id uuid.UUID) *LocalityDeleteOne { + builder := c.Delete().Where(locality.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &LocalityDeleteOne{builder} +} + +// Query returns a query builder for Locality. +func (c *LocalityClient) Query() *LocalityQuery { + return &LocalityQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeLocality}, + inters: c.Interceptors(), + } +} + +// Get returns a Locality entity by its id. +func (c *LocalityClient) Get(ctx context.Context, id uuid.UUID) (*Locality, error) { + return c.Query().Where(locality.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *LocalityClient) GetX(ctx context.Context, id uuid.UUID) *Locality { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryParentParish queries the parent_parish edge of a Locality. +func (c *LocalityClient) QueryParentParish(_m *Locality) *AdministrativeAreaQuery { + query := (&AdministrativeAreaClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(locality.Table, locality.FieldID, id), + sqlgraph.To(administrativearea.Table, administrativearea.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, locality.ParentParishTable, locality.ParentParishColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryProviderProfiles queries the provider_profiles edge of a Locality. +func (c *LocalityClient) QueryProviderProfiles(_m *Locality) *ProviderProfileQuery { + query := (&ProviderProfileClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(locality.Table, locality.FieldID, id), + sqlgraph.To(providerprofile.Table, providerprofile.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, locality.ProviderProfilesTable, locality.ProviderProfilesPrimaryKey...), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *LocalityClient) Hooks() []Hook { + return c.hooks.Locality +} + +// Interceptors returns the client interceptors. +func (c *LocalityClient) Interceptors() []Interceptor { + return c.inters.Locality +} + +func (c *LocalityClient) mutate(ctx context.Context, m *LocalityMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&LocalityCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&LocalityUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&LocalityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&LocalityDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown Locality mutation op: %q", m.Op()) + } +} + +// ProviderProfileClient is a client for the ProviderProfile schema. +type ProviderProfileClient struct { + config +} + +// NewProviderProfileClient returns a client for the ProviderProfile from the given config. +func NewProviderProfileClient(c config) *ProviderProfileClient { + return &ProviderProfileClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `providerprofile.Hooks(f(g(h())))`. +func (c *ProviderProfileClient) Use(hooks ...Hook) { + c.hooks.ProviderProfile = append(c.hooks.ProviderProfile, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `providerprofile.Intercept(f(g(h())))`. +func (c *ProviderProfileClient) Intercept(interceptors ...Interceptor) { + c.inters.ProviderProfile = append(c.inters.ProviderProfile, interceptors...) +} + +// Create returns a builder for creating a ProviderProfile entity. +func (c *ProviderProfileClient) Create() *ProviderProfileCreate { + mutation := newProviderProfileMutation(c.config, OpCreate) + return &ProviderProfileCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of ProviderProfile entities. +func (c *ProviderProfileClient) CreateBulk(builders ...*ProviderProfileCreate) *ProviderProfileCreateBulk { + return &ProviderProfileCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *ProviderProfileClient) MapCreateBulk(slice any, setFunc func(*ProviderProfileCreate, int)) *ProviderProfileCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &ProviderProfileCreateBulk{err: fmt.Errorf("calling to ProviderProfileClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*ProviderProfileCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &ProviderProfileCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for ProviderProfile. +func (c *ProviderProfileClient) Update() *ProviderProfileUpdate { + mutation := newProviderProfileMutation(c.config, OpUpdate) + return &ProviderProfileUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *ProviderProfileClient) UpdateOne(_m *ProviderProfile) *ProviderProfileUpdateOne { + mutation := newProviderProfileMutation(c.config, OpUpdateOne, withProviderProfile(_m)) + return &ProviderProfileUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *ProviderProfileClient) UpdateOneID(id uuid.UUID) *ProviderProfileUpdateOne { + mutation := newProviderProfileMutation(c.config, OpUpdateOne, withProviderProfileID(id)) + return &ProviderProfileUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for ProviderProfile. +func (c *ProviderProfileClient) Delete() *ProviderProfileDelete { + mutation := newProviderProfileMutation(c.config, OpDelete) + return &ProviderProfileDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *ProviderProfileClient) DeleteOne(_m *ProviderProfile) *ProviderProfileDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *ProviderProfileClient) DeleteOneID(id uuid.UUID) *ProviderProfileDeleteOne { + builder := c.Delete().Where(providerprofile.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &ProviderProfileDeleteOne{builder} +} + +// Query returns a query builder for ProviderProfile. +func (c *ProviderProfileClient) Query() *ProviderProfileQuery { + return &ProviderProfileQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeProviderProfile}, + inters: c.Interceptors(), + } +} + +// Get returns a ProviderProfile entity by its id. +func (c *ProviderProfileClient) Get(ctx context.Context, id uuid.UUID) (*ProviderProfile, error) { + return c.Query().Where(providerprofile.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *ProviderProfileClient) GetX(ctx context.Context, id uuid.UUID) *ProviderProfile { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryServiceLocalities queries the service_localities edge of a ProviderProfile. +func (c *ProviderProfileClient) QueryServiceLocalities(_m *ProviderProfile) *LocalityQuery { + query := (&LocalityClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(providerprofile.Table, providerprofile.FieldID, id), + sqlgraph.To(locality.Table, locality.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, providerprofile.ServiceLocalitiesTable, providerprofile.ServiceLocalitiesPrimaryKey...), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QuerySpokenLanguages queries the spoken_languages edge of a ProviderProfile. +func (c *ProviderProfileClient) QuerySpokenLanguages(_m *ProviderProfile) *SpokenLanguageQuery { + query := (&SpokenLanguageClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(providerprofile.Table, providerprofile.FieldID, id), + sqlgraph.To(spokenlanguage.Table, spokenlanguage.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, providerprofile.SpokenLanguagesTable, providerprofile.SpokenLanguagesPrimaryKey...), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryServiceLocalityLinks queries the service_locality_links edge of a ProviderProfile. +func (c *ProviderProfileClient) QueryServiceLocalityLinks(_m *ProviderProfile) *ProviderServiceLocalityQuery { + query := (&ProviderServiceLocalityClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(providerprofile.Table, providerprofile.FieldID, id), + sqlgraph.To(providerservicelocality.Table, providerservicelocality.ProfileColumn), + sqlgraph.Edge(sqlgraph.O2M, true, providerprofile.ServiceLocalityLinksTable, providerprofile.ServiceLocalityLinksColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QuerySpokenLanguageLinks queries the spoken_language_links edge of a ProviderProfile. +func (c *ProviderProfileClient) QuerySpokenLanguageLinks(_m *ProviderProfile) *ProviderSpokenLanguageQuery { + query := (&ProviderSpokenLanguageClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(providerprofile.Table, providerprofile.FieldID, id), + sqlgraph.To(providerspokenlanguage.Table, providerspokenlanguage.ProfileColumn), + sqlgraph.Edge(sqlgraph.O2M, true, providerprofile.SpokenLanguageLinksTable, providerprofile.SpokenLanguageLinksColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *ProviderProfileClient) Hooks() []Hook { + return c.hooks.ProviderProfile +} + +// Interceptors returns the client interceptors. +func (c *ProviderProfileClient) Interceptors() []Interceptor { + return c.inters.ProviderProfile +} + +func (c *ProviderProfileClient) mutate(ctx context.Context, m *ProviderProfileMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&ProviderProfileCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&ProviderProfileUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&ProviderProfileUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&ProviderProfileDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown ProviderProfile mutation op: %q", m.Op()) + } +} + +// ProviderServiceLocalityClient is a client for the ProviderServiceLocality schema. +type ProviderServiceLocalityClient struct { + config +} + +// NewProviderServiceLocalityClient returns a client for the ProviderServiceLocality from the given config. +func NewProviderServiceLocalityClient(c config) *ProviderServiceLocalityClient { + return &ProviderServiceLocalityClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `providerservicelocality.Hooks(f(g(h())))`. +func (c *ProviderServiceLocalityClient) Use(hooks ...Hook) { + c.hooks.ProviderServiceLocality = append(c.hooks.ProviderServiceLocality, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `providerservicelocality.Intercept(f(g(h())))`. +func (c *ProviderServiceLocalityClient) Intercept(interceptors ...Interceptor) { + c.inters.ProviderServiceLocality = append(c.inters.ProviderServiceLocality, interceptors...) +} + +// Create returns a builder for creating a ProviderServiceLocality entity. +func (c *ProviderServiceLocalityClient) Create() *ProviderServiceLocalityCreate { + mutation := newProviderServiceLocalityMutation(c.config, OpCreate) + return &ProviderServiceLocalityCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of ProviderServiceLocality entities. +func (c *ProviderServiceLocalityClient) CreateBulk(builders ...*ProviderServiceLocalityCreate) *ProviderServiceLocalityCreateBulk { + return &ProviderServiceLocalityCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *ProviderServiceLocalityClient) MapCreateBulk(slice any, setFunc func(*ProviderServiceLocalityCreate, int)) *ProviderServiceLocalityCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &ProviderServiceLocalityCreateBulk{err: fmt.Errorf("calling to ProviderServiceLocalityClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*ProviderServiceLocalityCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &ProviderServiceLocalityCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for ProviderServiceLocality. +func (c *ProviderServiceLocalityClient) Update() *ProviderServiceLocalityUpdate { + mutation := newProviderServiceLocalityMutation(c.config, OpUpdate) + return &ProviderServiceLocalityUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *ProviderServiceLocalityClient) UpdateOne(_m *ProviderServiceLocality) *ProviderServiceLocalityUpdateOne { + mutation := newProviderServiceLocalityMutation(c.config, OpUpdateOne) + mutation.profile = &_m.InternalUserID + mutation.locality = &_m.LocalityID + return &ProviderServiceLocalityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for ProviderServiceLocality. +func (c *ProviderServiceLocalityClient) Delete() *ProviderServiceLocalityDelete { + mutation := newProviderServiceLocalityMutation(c.config, OpDelete) + return &ProviderServiceLocalityDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Query returns a query builder for ProviderServiceLocality. +func (c *ProviderServiceLocalityClient) Query() *ProviderServiceLocalityQuery { + return &ProviderServiceLocalityQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeProviderServiceLocality}, + inters: c.Interceptors(), + } +} + +// QueryProfile queries the profile edge of a ProviderServiceLocality. +func (c *ProviderServiceLocalityClient) QueryProfile(_m *ProviderServiceLocality) *ProviderProfileQuery { + return c.Query(). + Where(providerservicelocality.InternalUserID(_m.InternalUserID), providerservicelocality.LocalityID(_m.LocalityID)). + QueryProfile() +} + +// QueryLocality queries the locality edge of a ProviderServiceLocality. +func (c *ProviderServiceLocalityClient) QueryLocality(_m *ProviderServiceLocality) *LocalityQuery { + return c.Query(). + Where(providerservicelocality.InternalUserID(_m.InternalUserID), providerservicelocality.LocalityID(_m.LocalityID)). + QueryLocality() +} + +// Hooks returns the client hooks. +func (c *ProviderServiceLocalityClient) Hooks() []Hook { + return c.hooks.ProviderServiceLocality +} + +// Interceptors returns the client interceptors. +func (c *ProviderServiceLocalityClient) Interceptors() []Interceptor { + return c.inters.ProviderServiceLocality +} + +func (c *ProviderServiceLocalityClient) mutate(ctx context.Context, m *ProviderServiceLocalityMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&ProviderServiceLocalityCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&ProviderServiceLocalityUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&ProviderServiceLocalityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&ProviderServiceLocalityDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown ProviderServiceLocality mutation op: %q", m.Op()) + } +} + +// ProviderSpokenLanguageClient is a client for the ProviderSpokenLanguage schema. +type ProviderSpokenLanguageClient struct { + config +} + +// NewProviderSpokenLanguageClient returns a client for the ProviderSpokenLanguage from the given config. +func NewProviderSpokenLanguageClient(c config) *ProviderSpokenLanguageClient { + return &ProviderSpokenLanguageClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `providerspokenlanguage.Hooks(f(g(h())))`. +func (c *ProviderSpokenLanguageClient) Use(hooks ...Hook) { + c.hooks.ProviderSpokenLanguage = append(c.hooks.ProviderSpokenLanguage, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `providerspokenlanguage.Intercept(f(g(h())))`. +func (c *ProviderSpokenLanguageClient) Intercept(interceptors ...Interceptor) { + c.inters.ProviderSpokenLanguage = append(c.inters.ProviderSpokenLanguage, interceptors...) +} + +// Create returns a builder for creating a ProviderSpokenLanguage entity. +func (c *ProviderSpokenLanguageClient) Create() *ProviderSpokenLanguageCreate { + mutation := newProviderSpokenLanguageMutation(c.config, OpCreate) + return &ProviderSpokenLanguageCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of ProviderSpokenLanguage entities. +func (c *ProviderSpokenLanguageClient) CreateBulk(builders ...*ProviderSpokenLanguageCreate) *ProviderSpokenLanguageCreateBulk { + return &ProviderSpokenLanguageCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *ProviderSpokenLanguageClient) MapCreateBulk(slice any, setFunc func(*ProviderSpokenLanguageCreate, int)) *ProviderSpokenLanguageCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &ProviderSpokenLanguageCreateBulk{err: fmt.Errorf("calling to ProviderSpokenLanguageClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*ProviderSpokenLanguageCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &ProviderSpokenLanguageCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for ProviderSpokenLanguage. +func (c *ProviderSpokenLanguageClient) Update() *ProviderSpokenLanguageUpdate { + mutation := newProviderSpokenLanguageMutation(c.config, OpUpdate) + return &ProviderSpokenLanguageUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *ProviderSpokenLanguageClient) UpdateOne(_m *ProviderSpokenLanguage) *ProviderSpokenLanguageUpdateOne { + mutation := newProviderSpokenLanguageMutation(c.config, OpUpdateOne) + mutation.profile = &_m.InternalUserID + mutation.language = &_m.LanguageCode + return &ProviderSpokenLanguageUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for ProviderSpokenLanguage. +func (c *ProviderSpokenLanguageClient) Delete() *ProviderSpokenLanguageDelete { + mutation := newProviderSpokenLanguageMutation(c.config, OpDelete) + return &ProviderSpokenLanguageDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Query returns a query builder for ProviderSpokenLanguage. +func (c *ProviderSpokenLanguageClient) Query() *ProviderSpokenLanguageQuery { + return &ProviderSpokenLanguageQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeProviderSpokenLanguage}, + inters: c.Interceptors(), + } +} + +// QueryProfile queries the profile edge of a ProviderSpokenLanguage. +func (c *ProviderSpokenLanguageClient) QueryProfile(_m *ProviderSpokenLanguage) *ProviderProfileQuery { + return c.Query(). + Where(providerspokenlanguage.InternalUserID(_m.InternalUserID), providerspokenlanguage.LanguageCode(_m.LanguageCode)). + QueryProfile() +} + +// QueryLanguage queries the language edge of a ProviderSpokenLanguage. +func (c *ProviderSpokenLanguageClient) QueryLanguage(_m *ProviderSpokenLanguage) *SpokenLanguageQuery { + return c.Query(). + Where(providerspokenlanguage.InternalUserID(_m.InternalUserID), providerspokenlanguage.LanguageCode(_m.LanguageCode)). + QueryLanguage() +} + +// Hooks returns the client hooks. +func (c *ProviderSpokenLanguageClient) Hooks() []Hook { + return c.hooks.ProviderSpokenLanguage +} + +// Interceptors returns the client interceptors. +func (c *ProviderSpokenLanguageClient) Interceptors() []Interceptor { + return c.inters.ProviderSpokenLanguage +} + +func (c *ProviderSpokenLanguageClient) mutate(ctx context.Context, m *ProviderSpokenLanguageMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&ProviderSpokenLanguageCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&ProviderSpokenLanguageUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&ProviderSpokenLanguageUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&ProviderSpokenLanguageDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown ProviderSpokenLanguage mutation op: %q", m.Op()) + } +} + +// ServiceCategoryClient is a client for the ServiceCategory schema. +type ServiceCategoryClient struct { + config +} + +// NewServiceCategoryClient returns a client for the ServiceCategory from the given config. +func NewServiceCategoryClient(c config) *ServiceCategoryClient { + return &ServiceCategoryClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `servicecategory.Hooks(f(g(h())))`. +func (c *ServiceCategoryClient) Use(hooks ...Hook) { + c.hooks.ServiceCategory = append(c.hooks.ServiceCategory, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `servicecategory.Intercept(f(g(h())))`. +func (c *ServiceCategoryClient) Intercept(interceptors ...Interceptor) { + c.inters.ServiceCategory = append(c.inters.ServiceCategory, interceptors...) +} + +// Create returns a builder for creating a ServiceCategory entity. +func (c *ServiceCategoryClient) Create() *ServiceCategoryCreate { + mutation := newServiceCategoryMutation(c.config, OpCreate) + return &ServiceCategoryCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of ServiceCategory entities. +func (c *ServiceCategoryClient) CreateBulk(builders ...*ServiceCategoryCreate) *ServiceCategoryCreateBulk { + return &ServiceCategoryCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *ServiceCategoryClient) MapCreateBulk(slice any, setFunc func(*ServiceCategoryCreate, int)) *ServiceCategoryCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &ServiceCategoryCreateBulk{err: fmt.Errorf("calling to ServiceCategoryClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*ServiceCategoryCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &ServiceCategoryCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for ServiceCategory. +func (c *ServiceCategoryClient) Update() *ServiceCategoryUpdate { + mutation := newServiceCategoryMutation(c.config, OpUpdate) + return &ServiceCategoryUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *ServiceCategoryClient) UpdateOne(_m *ServiceCategory) *ServiceCategoryUpdateOne { + mutation := newServiceCategoryMutation(c.config, OpUpdateOne, withServiceCategory(_m)) + return &ServiceCategoryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *ServiceCategoryClient) UpdateOneID(id uuid.UUID) *ServiceCategoryUpdateOne { + mutation := newServiceCategoryMutation(c.config, OpUpdateOne, withServiceCategoryID(id)) + return &ServiceCategoryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for ServiceCategory. +func (c *ServiceCategoryClient) Delete() *ServiceCategoryDelete { + mutation := newServiceCategoryMutation(c.config, OpDelete) + return &ServiceCategoryDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *ServiceCategoryClient) DeleteOne(_m *ServiceCategory) *ServiceCategoryDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *ServiceCategoryClient) DeleteOneID(id uuid.UUID) *ServiceCategoryDeleteOne { + builder := c.Delete().Where(servicecategory.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &ServiceCategoryDeleteOne{builder} +} + +// Query returns a query builder for ServiceCategory. +func (c *ServiceCategoryClient) Query() *ServiceCategoryQuery { + return &ServiceCategoryQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeServiceCategory}, + inters: c.Interceptors(), + } +} + +// Get returns a ServiceCategory entity by its id. +func (c *ServiceCategoryClient) Get(ctx context.Context, id uuid.UUID) (*ServiceCategory, error) { + return c.Query().Where(servicecategory.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *ServiceCategoryClient) GetX(ctx context.Context, id uuid.UUID) *ServiceCategory { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryChildren queries the children edge of a ServiceCategory. +func (c *ServiceCategoryClient) QueryChildren(_m *ServiceCategory) *ServiceCategoryQuery { + query := (&ServiceCategoryClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(servicecategory.Table, servicecategory.FieldID, id), + sqlgraph.To(servicecategory.Table, servicecategory.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, servicecategory.ChildrenTable, servicecategory.ChildrenColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryParent queries the parent edge of a ServiceCategory. +func (c *ServiceCategoryClient) QueryParent(_m *ServiceCategory) *ServiceCategoryQuery { + query := (&ServiceCategoryClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(servicecategory.Table, servicecategory.FieldID, id), + sqlgraph.To(servicecategory.Table, servicecategory.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, servicecategory.ParentTable, servicecategory.ParentColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryLocalizedIn queries the localized_in edge of a ServiceCategory. +func (c *ServiceCategoryClient) QueryLocalizedIn(_m *ServiceCategory) *SupportedLocaleQuery { + query := (&SupportedLocaleClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(servicecategory.Table, servicecategory.FieldID, id), + sqlgraph.To(supportedlocale.Table, supportedlocale.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, servicecategory.LocalizedInTable, servicecategory.LocalizedInPrimaryKey...), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryTranslations queries the translations edge of a ServiceCategory. +func (c *ServiceCategoryClient) QueryTranslations(_m *ServiceCategory) *ServiceCategoryTranslationQuery { + query := (&ServiceCategoryTranslationClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(servicecategory.Table, servicecategory.FieldID, id), + sqlgraph.To(servicecategorytranslation.Table, servicecategorytranslation.CategoryColumn), + sqlgraph.Edge(sqlgraph.O2M, true, servicecategory.TranslationsTable, servicecategory.TranslationsColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *ServiceCategoryClient) Hooks() []Hook { + return c.hooks.ServiceCategory +} + +// Interceptors returns the client interceptors. +func (c *ServiceCategoryClient) Interceptors() []Interceptor { + return c.inters.ServiceCategory +} + +func (c *ServiceCategoryClient) mutate(ctx context.Context, m *ServiceCategoryMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&ServiceCategoryCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&ServiceCategoryUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&ServiceCategoryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&ServiceCategoryDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown ServiceCategory mutation op: %q", m.Op()) + } +} + +// ServiceCategoryTranslationClient is a client for the ServiceCategoryTranslation schema. +type ServiceCategoryTranslationClient struct { + config +} + +// NewServiceCategoryTranslationClient returns a client for the ServiceCategoryTranslation from the given config. +func NewServiceCategoryTranslationClient(c config) *ServiceCategoryTranslationClient { + return &ServiceCategoryTranslationClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `servicecategorytranslation.Hooks(f(g(h())))`. +func (c *ServiceCategoryTranslationClient) Use(hooks ...Hook) { + c.hooks.ServiceCategoryTranslation = append(c.hooks.ServiceCategoryTranslation, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `servicecategorytranslation.Intercept(f(g(h())))`. +func (c *ServiceCategoryTranslationClient) Intercept(interceptors ...Interceptor) { + c.inters.ServiceCategoryTranslation = append(c.inters.ServiceCategoryTranslation, interceptors...) +} + +// Create returns a builder for creating a ServiceCategoryTranslation entity. +func (c *ServiceCategoryTranslationClient) Create() *ServiceCategoryTranslationCreate { + mutation := newServiceCategoryTranslationMutation(c.config, OpCreate) + return &ServiceCategoryTranslationCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of ServiceCategoryTranslation entities. +func (c *ServiceCategoryTranslationClient) CreateBulk(builders ...*ServiceCategoryTranslationCreate) *ServiceCategoryTranslationCreateBulk { + return &ServiceCategoryTranslationCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *ServiceCategoryTranslationClient) MapCreateBulk(slice any, setFunc func(*ServiceCategoryTranslationCreate, int)) *ServiceCategoryTranslationCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &ServiceCategoryTranslationCreateBulk{err: fmt.Errorf("calling to ServiceCategoryTranslationClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*ServiceCategoryTranslationCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &ServiceCategoryTranslationCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for ServiceCategoryTranslation. +func (c *ServiceCategoryTranslationClient) Update() *ServiceCategoryTranslationUpdate { + mutation := newServiceCategoryTranslationMutation(c.config, OpUpdate) + return &ServiceCategoryTranslationUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *ServiceCategoryTranslationClient) UpdateOne(_m *ServiceCategoryTranslation) *ServiceCategoryTranslationUpdateOne { + mutation := newServiceCategoryTranslationMutation(c.config, OpUpdateOne) + mutation.category = &_m.CategoryID + mutation.locale_record = &_m.Locale + return &ServiceCategoryTranslationUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for ServiceCategoryTranslation. +func (c *ServiceCategoryTranslationClient) Delete() *ServiceCategoryTranslationDelete { + mutation := newServiceCategoryTranslationMutation(c.config, OpDelete) + return &ServiceCategoryTranslationDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Query returns a query builder for ServiceCategoryTranslation. +func (c *ServiceCategoryTranslationClient) Query() *ServiceCategoryTranslationQuery { + return &ServiceCategoryTranslationQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeServiceCategoryTranslation}, + inters: c.Interceptors(), + } +} + +// QueryCategory queries the category edge of a ServiceCategoryTranslation. +func (c *ServiceCategoryTranslationClient) QueryCategory(_m *ServiceCategoryTranslation) *ServiceCategoryQuery { + return c.Query(). + Where(servicecategorytranslation.CategoryID(_m.CategoryID), servicecategorytranslation.Locale(_m.Locale)). + QueryCategory() +} + +// QueryLocaleRecord queries the locale_record edge of a ServiceCategoryTranslation. +func (c *ServiceCategoryTranslationClient) QueryLocaleRecord(_m *ServiceCategoryTranslation) *SupportedLocaleQuery { + return c.Query(). + Where(servicecategorytranslation.CategoryID(_m.CategoryID), servicecategorytranslation.Locale(_m.Locale)). + QueryLocaleRecord() +} + +// Hooks returns the client hooks. +func (c *ServiceCategoryTranslationClient) Hooks() []Hook { + return c.hooks.ServiceCategoryTranslation +} + +// Interceptors returns the client interceptors. +func (c *ServiceCategoryTranslationClient) Interceptors() []Interceptor { + return c.inters.ServiceCategoryTranslation +} + +func (c *ServiceCategoryTranslationClient) mutate(ctx context.Context, m *ServiceCategoryTranslationMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&ServiceCategoryTranslationCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&ServiceCategoryTranslationUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&ServiceCategoryTranslationUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&ServiceCategoryTranslationDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown ServiceCategoryTranslation mutation op: %q", m.Op()) + } +} + +// SpokenLanguageClient is a client for the SpokenLanguage schema. +type SpokenLanguageClient struct { + config +} + +// NewSpokenLanguageClient returns a client for the SpokenLanguage from the given config. +func NewSpokenLanguageClient(c config) *SpokenLanguageClient { + return &SpokenLanguageClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `spokenlanguage.Hooks(f(g(h())))`. +func (c *SpokenLanguageClient) Use(hooks ...Hook) { + c.hooks.SpokenLanguage = append(c.hooks.SpokenLanguage, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `spokenlanguage.Intercept(f(g(h())))`. +func (c *SpokenLanguageClient) Intercept(interceptors ...Interceptor) { + c.inters.SpokenLanguage = append(c.inters.SpokenLanguage, interceptors...) +} + +// Create returns a builder for creating a SpokenLanguage entity. +func (c *SpokenLanguageClient) Create() *SpokenLanguageCreate { + mutation := newSpokenLanguageMutation(c.config, OpCreate) + return &SpokenLanguageCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of SpokenLanguage entities. +func (c *SpokenLanguageClient) CreateBulk(builders ...*SpokenLanguageCreate) *SpokenLanguageCreateBulk { + return &SpokenLanguageCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *SpokenLanguageClient) MapCreateBulk(slice any, setFunc func(*SpokenLanguageCreate, int)) *SpokenLanguageCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &SpokenLanguageCreateBulk{err: fmt.Errorf("calling to SpokenLanguageClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*SpokenLanguageCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &SpokenLanguageCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for SpokenLanguage. +func (c *SpokenLanguageClient) Update() *SpokenLanguageUpdate { + mutation := newSpokenLanguageMutation(c.config, OpUpdate) + return &SpokenLanguageUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *SpokenLanguageClient) UpdateOne(_m *SpokenLanguage) *SpokenLanguageUpdateOne { + mutation := newSpokenLanguageMutation(c.config, OpUpdateOne, withSpokenLanguage(_m)) + return &SpokenLanguageUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *SpokenLanguageClient) UpdateOneID(id string) *SpokenLanguageUpdateOne { + mutation := newSpokenLanguageMutation(c.config, OpUpdateOne, withSpokenLanguageID(id)) + return &SpokenLanguageUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for SpokenLanguage. +func (c *SpokenLanguageClient) Delete() *SpokenLanguageDelete { + mutation := newSpokenLanguageMutation(c.config, OpDelete) + return &SpokenLanguageDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *SpokenLanguageClient) DeleteOne(_m *SpokenLanguage) *SpokenLanguageDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *SpokenLanguageClient) DeleteOneID(id string) *SpokenLanguageDeleteOne { + builder := c.Delete().Where(spokenlanguage.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &SpokenLanguageDeleteOne{builder} +} + +// Query returns a query builder for SpokenLanguage. +func (c *SpokenLanguageClient) Query() *SpokenLanguageQuery { + return &SpokenLanguageQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeSpokenLanguage}, + inters: c.Interceptors(), + } +} + +// Get returns a SpokenLanguage entity by its id. +func (c *SpokenLanguageClient) Get(ctx context.Context, id string) (*SpokenLanguage, error) { + return c.Query().Where(spokenlanguage.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *SpokenLanguageClient) GetX(ctx context.Context, id string) *SpokenLanguage { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryLocalizedIn queries the localized_in edge of a SpokenLanguage. +func (c *SpokenLanguageClient) QueryLocalizedIn(_m *SpokenLanguage) *SupportedLocaleQuery { + query := (&SupportedLocaleClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(spokenlanguage.Table, spokenlanguage.FieldID, id), + sqlgraph.To(supportedlocale.Table, supportedlocale.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, spokenlanguage.LocalizedInTable, spokenlanguage.LocalizedInPrimaryKey...), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryProviderProfiles queries the provider_profiles edge of a SpokenLanguage. +func (c *SpokenLanguageClient) QueryProviderProfiles(_m *SpokenLanguage) *ProviderProfileQuery { + query := (&ProviderProfileClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(spokenlanguage.Table, spokenlanguage.FieldID, id), + sqlgraph.To(providerprofile.Table, providerprofile.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, spokenlanguage.ProviderProfilesTable, spokenlanguage.ProviderProfilesPrimaryKey...), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryTranslations queries the translations edge of a SpokenLanguage. +func (c *SpokenLanguageClient) QueryTranslations(_m *SpokenLanguage) *SpokenLanguageTranslationQuery { + query := (&SpokenLanguageTranslationClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(spokenlanguage.Table, spokenlanguage.FieldID, id), + sqlgraph.To(spokenlanguagetranslation.Table, spokenlanguagetranslation.LanguageColumn), + sqlgraph.Edge(sqlgraph.O2M, true, spokenlanguage.TranslationsTable, spokenlanguage.TranslationsColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *SpokenLanguageClient) Hooks() []Hook { + return c.hooks.SpokenLanguage +} + +// Interceptors returns the client interceptors. +func (c *SpokenLanguageClient) Interceptors() []Interceptor { + return c.inters.SpokenLanguage +} + +func (c *SpokenLanguageClient) mutate(ctx context.Context, m *SpokenLanguageMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&SpokenLanguageCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&SpokenLanguageUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&SpokenLanguageUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&SpokenLanguageDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown SpokenLanguage mutation op: %q", m.Op()) + } +} + +// SpokenLanguageTranslationClient is a client for the SpokenLanguageTranslation schema. +type SpokenLanguageTranslationClient struct { + config +} + +// NewSpokenLanguageTranslationClient returns a client for the SpokenLanguageTranslation from the given config. +func NewSpokenLanguageTranslationClient(c config) *SpokenLanguageTranslationClient { + return &SpokenLanguageTranslationClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `spokenlanguagetranslation.Hooks(f(g(h())))`. +func (c *SpokenLanguageTranslationClient) Use(hooks ...Hook) { + c.hooks.SpokenLanguageTranslation = append(c.hooks.SpokenLanguageTranslation, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `spokenlanguagetranslation.Intercept(f(g(h())))`. +func (c *SpokenLanguageTranslationClient) Intercept(interceptors ...Interceptor) { + c.inters.SpokenLanguageTranslation = append(c.inters.SpokenLanguageTranslation, interceptors...) +} + +// Create returns a builder for creating a SpokenLanguageTranslation entity. +func (c *SpokenLanguageTranslationClient) Create() *SpokenLanguageTranslationCreate { + mutation := newSpokenLanguageTranslationMutation(c.config, OpCreate) + return &SpokenLanguageTranslationCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of SpokenLanguageTranslation entities. +func (c *SpokenLanguageTranslationClient) CreateBulk(builders ...*SpokenLanguageTranslationCreate) *SpokenLanguageTranslationCreateBulk { + return &SpokenLanguageTranslationCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *SpokenLanguageTranslationClient) MapCreateBulk(slice any, setFunc func(*SpokenLanguageTranslationCreate, int)) *SpokenLanguageTranslationCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &SpokenLanguageTranslationCreateBulk{err: fmt.Errorf("calling to SpokenLanguageTranslationClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*SpokenLanguageTranslationCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &SpokenLanguageTranslationCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for SpokenLanguageTranslation. +func (c *SpokenLanguageTranslationClient) Update() *SpokenLanguageTranslationUpdate { + mutation := newSpokenLanguageTranslationMutation(c.config, OpUpdate) + return &SpokenLanguageTranslationUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *SpokenLanguageTranslationClient) UpdateOne(_m *SpokenLanguageTranslation) *SpokenLanguageTranslationUpdateOne { + mutation := newSpokenLanguageTranslationMutation(c.config, OpUpdateOne) + mutation.language = &_m.LanguageCode + mutation.locale_record = &_m.Locale + return &SpokenLanguageTranslationUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for SpokenLanguageTranslation. +func (c *SpokenLanguageTranslationClient) Delete() *SpokenLanguageTranslationDelete { + mutation := newSpokenLanguageTranslationMutation(c.config, OpDelete) + return &SpokenLanguageTranslationDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Query returns a query builder for SpokenLanguageTranslation. +func (c *SpokenLanguageTranslationClient) Query() *SpokenLanguageTranslationQuery { + return &SpokenLanguageTranslationQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeSpokenLanguageTranslation}, + inters: c.Interceptors(), + } +} + +// QueryLanguage queries the language edge of a SpokenLanguageTranslation. +func (c *SpokenLanguageTranslationClient) QueryLanguage(_m *SpokenLanguageTranslation) *SpokenLanguageQuery { + return c.Query(). + Where(spokenlanguagetranslation.LanguageCode(_m.LanguageCode), spokenlanguagetranslation.Locale(_m.Locale)). + QueryLanguage() +} + +// QueryLocaleRecord queries the locale_record edge of a SpokenLanguageTranslation. +func (c *SpokenLanguageTranslationClient) QueryLocaleRecord(_m *SpokenLanguageTranslation) *SupportedLocaleQuery { + return c.Query(). + Where(spokenlanguagetranslation.LanguageCode(_m.LanguageCode), spokenlanguagetranslation.Locale(_m.Locale)). + QueryLocaleRecord() +} + +// Hooks returns the client hooks. +func (c *SpokenLanguageTranslationClient) Hooks() []Hook { + return c.hooks.SpokenLanguageTranslation +} + +// Interceptors returns the client interceptors. +func (c *SpokenLanguageTranslationClient) Interceptors() []Interceptor { + return c.inters.SpokenLanguageTranslation +} + +func (c *SpokenLanguageTranslationClient) mutate(ctx context.Context, m *SpokenLanguageTranslationMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&SpokenLanguageTranslationCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&SpokenLanguageTranslationUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&SpokenLanguageTranslationUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&SpokenLanguageTranslationDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown SpokenLanguageTranslation mutation op: %q", m.Op()) + } +} + +// SupportedLocaleClient is a client for the SupportedLocale schema. +type SupportedLocaleClient struct { + config +} + +// NewSupportedLocaleClient returns a client for the SupportedLocale from the given config. +func NewSupportedLocaleClient(c config) *SupportedLocaleClient { + return &SupportedLocaleClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `supportedlocale.Hooks(f(g(h())))`. +func (c *SupportedLocaleClient) Use(hooks ...Hook) { + c.hooks.SupportedLocale = append(c.hooks.SupportedLocale, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `supportedlocale.Intercept(f(g(h())))`. +func (c *SupportedLocaleClient) Intercept(interceptors ...Interceptor) { + c.inters.SupportedLocale = append(c.inters.SupportedLocale, interceptors...) +} + +// Create returns a builder for creating a SupportedLocale entity. +func (c *SupportedLocaleClient) Create() *SupportedLocaleCreate { + mutation := newSupportedLocaleMutation(c.config, OpCreate) + return &SupportedLocaleCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of SupportedLocale entities. +func (c *SupportedLocaleClient) CreateBulk(builders ...*SupportedLocaleCreate) *SupportedLocaleCreateBulk { + return &SupportedLocaleCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *SupportedLocaleClient) MapCreateBulk(slice any, setFunc func(*SupportedLocaleCreate, int)) *SupportedLocaleCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &SupportedLocaleCreateBulk{err: fmt.Errorf("calling to SupportedLocaleClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*SupportedLocaleCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &SupportedLocaleCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for SupportedLocale. +func (c *SupportedLocaleClient) Update() *SupportedLocaleUpdate { + mutation := newSupportedLocaleMutation(c.config, OpUpdate) + return &SupportedLocaleUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *SupportedLocaleClient) UpdateOne(_m *SupportedLocale) *SupportedLocaleUpdateOne { + mutation := newSupportedLocaleMutation(c.config, OpUpdateOne, withSupportedLocale(_m)) + return &SupportedLocaleUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *SupportedLocaleClient) UpdateOneID(id string) *SupportedLocaleUpdateOne { + mutation := newSupportedLocaleMutation(c.config, OpUpdateOne, withSupportedLocaleID(id)) + return &SupportedLocaleUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for SupportedLocale. +func (c *SupportedLocaleClient) Delete() *SupportedLocaleDelete { + mutation := newSupportedLocaleMutation(c.config, OpDelete) + return &SupportedLocaleDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *SupportedLocaleClient) DeleteOne(_m *SupportedLocale) *SupportedLocaleDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *SupportedLocaleClient) DeleteOneID(id string) *SupportedLocaleDeleteOne { + builder := c.Delete().Where(supportedlocale.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &SupportedLocaleDeleteOne{builder} +} + +// Query returns a query builder for SupportedLocale. +func (c *SupportedLocaleClient) Query() *SupportedLocaleQuery { + return &SupportedLocaleQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeSupportedLocale}, + inters: c.Interceptors(), + } +} + +// Get returns a SupportedLocale entity by its id. +func (c *SupportedLocaleClient) Get(ctx context.Context, id string) (*SupportedLocale, error) { + return c.Query().Where(supportedlocale.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *SupportedLocaleClient) GetX(ctx context.Context, id string) *SupportedLocale { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryTranslatedCategories queries the translated_categories edge of a SupportedLocale. +func (c *SupportedLocaleClient) QueryTranslatedCategories(_m *SupportedLocale) *ServiceCategoryQuery { + query := (&ServiceCategoryClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(supportedlocale.Table, supportedlocale.FieldID, id), + sqlgraph.To(servicecategory.Table, servicecategory.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, supportedlocale.TranslatedCategoriesTable, supportedlocale.TranslatedCategoriesPrimaryKey...), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryTranslatedLanguages queries the translated_languages edge of a SupportedLocale. +func (c *SupportedLocaleClient) QueryTranslatedLanguages(_m *SupportedLocale) *SpokenLanguageQuery { + query := (&SpokenLanguageClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(supportedlocale.Table, supportedlocale.FieldID, id), + sqlgraph.To(spokenlanguage.Table, spokenlanguage.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, supportedlocale.TranslatedLanguagesTable, supportedlocale.TranslatedLanguagesPrimaryKey...), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *SupportedLocaleClient) Hooks() []Hook { + return c.hooks.SupportedLocale +} + +// Interceptors returns the client interceptors. +func (c *SupportedLocaleClient) Interceptors() []Interceptor { + return c.inters.SupportedLocale +} + +func (c *SupportedLocaleClient) mutate(ctx context.Context, m *SupportedLocaleMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&SupportedLocaleCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&SupportedLocaleUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&SupportedLocaleUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&SupportedLocaleDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown SupportedLocale mutation op: %q", m.Op()) + } +} + // UserAccountClient is a client for the UserAccount schema. type UserAccountClient struct { config @@ -476,9 +2119,15 @@ func (c *UserAccountClient) mutate(ctx context.Context, m *UserAccountMutation) // hooks and interceptors per client, for fast access. type ( hooks struct { - InternalUser, UserAccount []ent.Hook + AdministrativeArea, InternalUser, Locality, ProviderProfile, + ProviderServiceLocality, ProviderSpokenLanguage, ServiceCategory, + ServiceCategoryTranslation, SpokenLanguage, SpokenLanguageTranslation, + SupportedLocale, UserAccount []ent.Hook } inters struct { - InternalUser, UserAccount []ent.Interceptor + AdministrativeArea, InternalUser, Locality, ProviderProfile, + ProviderServiceLocality, ProviderSpokenLanguage, ServiceCategory, + ServiceCategoryTranslation, SpokenLanguage, SpokenLanguageTranslation, + SupportedLocale, UserAccount []ent.Interceptor } ) diff --git a/backend/ent/ent.go b/backend/ent/ent.go index e6debe5..b7bb393 100644 --- a/backend/ent/ent.go +++ b/backend/ent/ent.go @@ -12,7 +12,17 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/administrativearea" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerservicelocality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerspokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategorytranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguagetranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" ) @@ -74,8 +84,18 @@ var ( func checkColumn(t, c string) error { initCheck.Do(func() { columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ - internaluser.Table: internaluser.ValidColumn, - useraccount.Table: useraccount.ValidColumn, + administrativearea.Table: administrativearea.ValidColumn, + internaluser.Table: internaluser.ValidColumn, + locality.Table: locality.ValidColumn, + providerprofile.Table: providerprofile.ValidColumn, + providerservicelocality.Table: providerservicelocality.ValidColumn, + providerspokenlanguage.Table: providerspokenlanguage.ValidColumn, + servicecategory.Table: servicecategory.ValidColumn, + servicecategorytranslation.Table: servicecategorytranslation.ValidColumn, + spokenlanguage.Table: spokenlanguage.ValidColumn, + spokenlanguagetranslation.Table: spokenlanguagetranslation.ValidColumn, + supportedlocale.Table: supportedlocale.ValidColumn, + useraccount.Table: useraccount.ValidColumn, }) }) return columnCheck(t, c) diff --git a/backend/ent/hook/hook.go b/backend/ent/hook/hook.go index 66cfec8..2418381 100644 --- a/backend/ent/hook/hook.go +++ b/backend/ent/hook/hook.go @@ -9,6 +9,18 @@ import ( "github.com/SourceSenseiTheRealOne/juntly/backend/ent" ) +// The AdministrativeAreaFunc type is an adapter to allow the use of ordinary +// function as AdministrativeArea mutator. +type AdministrativeAreaFunc func(context.Context, *ent.AdministrativeAreaMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f AdministrativeAreaFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.AdministrativeAreaMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AdministrativeAreaMutation", m) +} + // The InternalUserFunc type is an adapter to allow the use of ordinary // function as InternalUser mutator. type InternalUserFunc func(context.Context, *ent.InternalUserMutation) (ent.Value, error) @@ -21,6 +33,114 @@ func (f InternalUserFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.InternalUserMutation", m) } +// The LocalityFunc type is an adapter to allow the use of ordinary +// function as Locality mutator. +type LocalityFunc func(context.Context, *ent.LocalityMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f LocalityFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.LocalityMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.LocalityMutation", m) +} + +// The ProviderProfileFunc type is an adapter to allow the use of ordinary +// function as ProviderProfile mutator. +type ProviderProfileFunc func(context.Context, *ent.ProviderProfileMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f ProviderProfileFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.ProviderProfileMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ProviderProfileMutation", m) +} + +// The ProviderServiceLocalityFunc type is an adapter to allow the use of ordinary +// function as ProviderServiceLocality mutator. +type ProviderServiceLocalityFunc func(context.Context, *ent.ProviderServiceLocalityMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f ProviderServiceLocalityFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.ProviderServiceLocalityMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ProviderServiceLocalityMutation", m) +} + +// The ProviderSpokenLanguageFunc type is an adapter to allow the use of ordinary +// function as ProviderSpokenLanguage mutator. +type ProviderSpokenLanguageFunc func(context.Context, *ent.ProviderSpokenLanguageMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f ProviderSpokenLanguageFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.ProviderSpokenLanguageMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ProviderSpokenLanguageMutation", m) +} + +// The ServiceCategoryFunc type is an adapter to allow the use of ordinary +// function as ServiceCategory mutator. +type ServiceCategoryFunc func(context.Context, *ent.ServiceCategoryMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f ServiceCategoryFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.ServiceCategoryMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ServiceCategoryMutation", m) +} + +// The ServiceCategoryTranslationFunc type is an adapter to allow the use of ordinary +// function as ServiceCategoryTranslation mutator. +type ServiceCategoryTranslationFunc func(context.Context, *ent.ServiceCategoryTranslationMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f ServiceCategoryTranslationFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.ServiceCategoryTranslationMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ServiceCategoryTranslationMutation", m) +} + +// The SpokenLanguageFunc type is an adapter to allow the use of ordinary +// function as SpokenLanguage mutator. +type SpokenLanguageFunc func(context.Context, *ent.SpokenLanguageMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f SpokenLanguageFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.SpokenLanguageMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SpokenLanguageMutation", m) +} + +// The SpokenLanguageTranslationFunc type is an adapter to allow the use of ordinary +// function as SpokenLanguageTranslation mutator. +type SpokenLanguageTranslationFunc func(context.Context, *ent.SpokenLanguageTranslationMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f SpokenLanguageTranslationFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.SpokenLanguageTranslationMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SpokenLanguageTranslationMutation", m) +} + +// The SupportedLocaleFunc type is an adapter to allow the use of ordinary +// function as SupportedLocale mutator. +type SupportedLocaleFunc func(context.Context, *ent.SupportedLocaleMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f SupportedLocaleFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.SupportedLocaleMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SupportedLocaleMutation", m) +} + // The UserAccountFunc type is an adapter to allow the use of ordinary // function as UserAccount mutator. type UserAccountFunc func(context.Context, *ent.UserAccountMutation) (ent.Value, error) diff --git a/backend/ent/locality.go b/backend/ent/locality.go new file mode 100644 index 0000000..7806339 --- /dev/null +++ b/backend/ent/locality.go @@ -0,0 +1,277 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/administrativearea" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/google/uuid" +) + +// Locality is the model entity for the Locality schema. +type Locality struct { + config `json:"-"` + // ID of the ent. + ID uuid.UUID `json:"id,omitempty"` + // Slug holds the value of the "slug" field. + Slug string `json:"slug,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` + // ParentParishID holds the value of the "parent_parish_id" field. + ParentParishID uuid.UUID `json:"parent_parish_id,omitempty"` + // Source holds the value of the "source" field. + Source string `json:"source,omitempty"` + // SourceElementID holds the value of the "source_element_id" field. + SourceElementID string `json:"source_element_id,omitempty"` + // SourceVersion holds the value of the "source_version" field. + SourceVersion string `json:"source_version,omitempty"` + // SourceRetrievedAt holds the value of the "source_retrieved_at" field. + SourceRetrievedAt time.Time `json:"source_retrieved_at,omitempty"` + // Latitude holds the value of the "latitude" field. + Latitude float64 `json:"latitude,omitempty"` + // Longitude holds the value of the "longitude" field. + Longitude float64 `json:"longitude,omitempty"` + // Active holds the value of the "active" field. + Active bool `json:"active,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the LocalityQuery when eager-loading is set. + Edges LocalityEdges `json:"edges"` + selectValues sql.SelectValues +} + +// LocalityEdges holds the relations/edges for other nodes in the graph. +type LocalityEdges struct { + // ParentParish holds the value of the parent_parish edge. + ParentParish *AdministrativeArea `json:"parent_parish,omitempty"` + // ProviderProfiles holds the value of the provider_profiles edge. + ProviderProfiles []*ProviderProfile `json:"provider_profiles,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [2]bool +} + +// ParentParishOrErr returns the ParentParish value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e LocalityEdges) ParentParishOrErr() (*AdministrativeArea, error) { + if e.ParentParish != nil { + return e.ParentParish, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: administrativearea.Label} + } + return nil, &NotLoadedError{edge: "parent_parish"} +} + +// ProviderProfilesOrErr returns the ProviderProfiles value or an error if the edge +// was not loaded in eager-loading. +func (e LocalityEdges) ProviderProfilesOrErr() ([]*ProviderProfile, error) { + if e.loadedTypes[1] { + return e.ProviderProfiles, nil + } + return nil, &NotLoadedError{edge: "provider_profiles"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*Locality) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case locality.FieldActive: + values[i] = new(sql.NullBool) + case locality.FieldLatitude, locality.FieldLongitude: + values[i] = new(sql.NullFloat64) + case locality.FieldSlug, locality.FieldName, locality.FieldSource, locality.FieldSourceElementID, locality.FieldSourceVersion: + values[i] = new(sql.NullString) + case locality.FieldSourceRetrievedAt, locality.FieldCreatedAt, locality.FieldUpdatedAt: + values[i] = new(sql.NullTime) + case locality.FieldID, locality.FieldParentParishID: + values[i] = new(uuid.UUID) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the Locality fields. +func (_m *Locality) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case locality.FieldID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value != nil { + _m.ID = *value + } + case locality.FieldSlug: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field slug", values[i]) + } else if value.Valid { + _m.Slug = value.String + } + case locality.FieldName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field name", values[i]) + } else if value.Valid { + _m.Name = value.String + } + case locality.FieldParentParishID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field parent_parish_id", values[i]) + } else if value != nil { + _m.ParentParishID = *value + } + case locality.FieldSource: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field source", values[i]) + } else if value.Valid { + _m.Source = value.String + } + case locality.FieldSourceElementID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field source_element_id", values[i]) + } else if value.Valid { + _m.SourceElementID = value.String + } + case locality.FieldSourceVersion: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field source_version", values[i]) + } else if value.Valid { + _m.SourceVersion = value.String + } + case locality.FieldSourceRetrievedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field source_retrieved_at", values[i]) + } else if value.Valid { + _m.SourceRetrievedAt = value.Time + } + case locality.FieldLatitude: + if value, ok := values[i].(*sql.NullFloat64); !ok { + return fmt.Errorf("unexpected type %T for field latitude", values[i]) + } else if value.Valid { + _m.Latitude = value.Float64 + } + case locality.FieldLongitude: + if value, ok := values[i].(*sql.NullFloat64); !ok { + return fmt.Errorf("unexpected type %T for field longitude", values[i]) + } else if value.Valid { + _m.Longitude = value.Float64 + } + case locality.FieldActive: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field active", values[i]) + } else if value.Valid { + _m.Active = value.Bool + } + case locality.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + _m.CreatedAt = value.Time + } + case locality.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + _m.UpdatedAt = value.Time + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the Locality. +// This includes values selected through modifiers, order, etc. +func (_m *Locality) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QueryParentParish queries the "parent_parish" edge of the Locality entity. +func (_m *Locality) QueryParentParish() *AdministrativeAreaQuery { + return NewLocalityClient(_m.config).QueryParentParish(_m) +} + +// QueryProviderProfiles queries the "provider_profiles" edge of the Locality entity. +func (_m *Locality) QueryProviderProfiles() *ProviderProfileQuery { + return NewLocalityClient(_m.config).QueryProviderProfiles(_m) +} + +// Update returns a builder for updating this Locality. +// Note that you need to call Locality.Unwrap() before calling this method if this Locality +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *Locality) Update() *LocalityUpdateOne { + return NewLocalityClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the Locality entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *Locality) Unwrap() *Locality { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: Locality is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *Locality) String() string { + var builder strings.Builder + builder.WriteString("Locality(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("slug=") + builder.WriteString(_m.Slug) + builder.WriteString(", ") + builder.WriteString("name=") + builder.WriteString(_m.Name) + builder.WriteString(", ") + builder.WriteString("parent_parish_id=") + builder.WriteString(fmt.Sprintf("%v", _m.ParentParishID)) + builder.WriteString(", ") + builder.WriteString("source=") + builder.WriteString(_m.Source) + builder.WriteString(", ") + builder.WriteString("source_element_id=") + builder.WriteString(_m.SourceElementID) + builder.WriteString(", ") + builder.WriteString("source_version=") + builder.WriteString(_m.SourceVersion) + builder.WriteString(", ") + builder.WriteString("source_retrieved_at=") + builder.WriteString(_m.SourceRetrievedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("latitude=") + builder.WriteString(fmt.Sprintf("%v", _m.Latitude)) + builder.WriteString(", ") + builder.WriteString("longitude=") + builder.WriteString(fmt.Sprintf("%v", _m.Longitude)) + builder.WriteString(", ") + builder.WriteString("active=") + builder.WriteString(fmt.Sprintf("%v", _m.Active)) + builder.WriteString(", ") + builder.WriteString("created_at=") + builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(_m.UpdatedAt.Format(time.ANSIC)) + builder.WriteByte(')') + return builder.String() +} + +// Localities is a parsable slice of Locality. +type Localities []*Locality diff --git a/backend/ent/locality/locality.go b/backend/ent/locality/locality.go new file mode 100644 index 0000000..58ac867 --- /dev/null +++ b/backend/ent/locality/locality.go @@ -0,0 +1,225 @@ +// Code generated by ent, DO NOT EDIT. + +package locality + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/google/uuid" +) + +const ( + // Label holds the string label denoting the locality type in the database. + Label = "locality" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldSlug holds the string denoting the slug field in the database. + FieldSlug = "slug" + // FieldName holds the string denoting the name field in the database. + FieldName = "name" + // FieldParentParishID holds the string denoting the parent_parish_id field in the database. + FieldParentParishID = "parent_parish_id" + // FieldSource holds the string denoting the source field in the database. + FieldSource = "source" + // FieldSourceElementID holds the string denoting the source_element_id field in the database. + FieldSourceElementID = "source_element_id" + // FieldSourceVersion holds the string denoting the source_version field in the database. + FieldSourceVersion = "source_version" + // FieldSourceRetrievedAt holds the string denoting the source_retrieved_at field in the database. + FieldSourceRetrievedAt = "source_retrieved_at" + // FieldLatitude holds the string denoting the latitude field in the database. + FieldLatitude = "latitude" + // FieldLongitude holds the string denoting the longitude field in the database. + FieldLongitude = "longitude" + // FieldActive holds the string denoting the active field in the database. + FieldActive = "active" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // EdgeParentParish holds the string denoting the parent_parish edge name in mutations. + EdgeParentParish = "parent_parish" + // EdgeProviderProfiles holds the string denoting the provider_profiles edge name in mutations. + EdgeProviderProfiles = "provider_profiles" + // ProviderProfileFieldID holds the string denoting the ID field of the ProviderProfile. + ProviderProfileFieldID = "internal_user_id" + // Table holds the table name of the locality in the database. + Table = "localities" + // ParentParishTable is the table that holds the parent_parish relation/edge. + ParentParishTable = "localities" + // ParentParishInverseTable is the table name for the AdministrativeArea entity. + // It exists in this package in order to avoid circular dependency with the "administrativearea" package. + ParentParishInverseTable = "administrative_areas" + // ParentParishColumn is the table column denoting the parent_parish relation/edge. + ParentParishColumn = "parent_parish_id" + // ProviderProfilesTable is the table that holds the provider_profiles relation/edge. The primary key declared below. + ProviderProfilesTable = "provider_service_localities" + // ProviderProfilesInverseTable is the table name for the ProviderProfile entity. + // It exists in this package in order to avoid circular dependency with the "providerprofile" package. + ProviderProfilesInverseTable = "provider_profiles" +) + +// Columns holds all SQL columns for locality fields. +var Columns = []string{ + FieldID, + FieldSlug, + FieldName, + FieldParentParishID, + FieldSource, + FieldSourceElementID, + FieldSourceVersion, + FieldSourceRetrievedAt, + FieldLatitude, + FieldLongitude, + FieldActive, + FieldCreatedAt, + FieldUpdatedAt, +} + +var ( + // ProviderProfilesPrimaryKey and ProviderProfilesColumn2 are the table columns denoting the + // primary key for the provider_profiles relation (M2M). + ProviderProfilesPrimaryKey = []string{"internal_user_id", "locality_id"} +) + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // SlugValidator is a validator for the "slug" field. It is called by the builders before save. + SlugValidator func(string) error + // NameValidator is a validator for the "name" field. It is called by the builders before save. + NameValidator func(string) error + // SourceValidator is a validator for the "source" field. It is called by the builders before save. + SourceValidator func(string) error + // SourceElementIDValidator is a validator for the "source_element_id" field. It is called by the builders before save. + SourceElementIDValidator func(string) error + // SourceVersionValidator is a validator for the "source_version" field. It is called by the builders before save. + SourceVersionValidator func(string) error + // LatitudeValidator is a validator for the "latitude" field. It is called by the builders before save. + LatitudeValidator func(float64) error + // LongitudeValidator is a validator for the "longitude" field. It is called by the builders before save. + LongitudeValidator func(float64) error + // DefaultActive holds the default value on creation for the "active" field. + DefaultActive bool + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() uuid.UUID +) + +// OrderOption defines the ordering options for the Locality queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// BySlug orders the results by the slug field. +func BySlug(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSlug, opts...).ToFunc() +} + +// ByName orders the results by the name field. +func ByName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldName, opts...).ToFunc() +} + +// ByParentParishID orders the results by the parent_parish_id field. +func ByParentParishID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldParentParishID, opts...).ToFunc() +} + +// BySource orders the results by the source field. +func BySource(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSource, opts...).ToFunc() +} + +// BySourceElementID orders the results by the source_element_id field. +func BySourceElementID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSourceElementID, opts...).ToFunc() +} + +// BySourceVersion orders the results by the source_version field. +func BySourceVersion(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSourceVersion, opts...).ToFunc() +} + +// BySourceRetrievedAt orders the results by the source_retrieved_at field. +func BySourceRetrievedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSourceRetrievedAt, opts...).ToFunc() +} + +// ByLatitude orders the results by the latitude field. +func ByLatitude(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLatitude, opts...).ToFunc() +} + +// ByLongitude orders the results by the longitude field. +func ByLongitude(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLongitude, opts...).ToFunc() +} + +// ByActive orders the results by the active field. +func ByActive(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldActive, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByParentParishField orders the results by parent_parish field. +func ByParentParishField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newParentParishStep(), sql.OrderByField(field, opts...)) + } +} + +// ByProviderProfilesCount orders the results by provider_profiles count. +func ByProviderProfilesCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newProviderProfilesStep(), opts...) + } +} + +// ByProviderProfiles orders the results by provider_profiles terms. +func ByProviderProfiles(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newProviderProfilesStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} +func newParentParishStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ParentParishInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, ParentParishTable, ParentParishColumn), + ) +} +func newProviderProfilesStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ProviderProfilesInverseTable, ProviderProfileFieldID), + sqlgraph.Edge(sqlgraph.M2M, true, ProviderProfilesTable, ProviderProfilesPrimaryKey...), + ) +} diff --git a/backend/ent/locality/where.go b/backend/ent/locality/where.go new file mode 100644 index 0000000..51bb4d4 --- /dev/null +++ b/backend/ent/locality/where.go @@ -0,0 +1,733 @@ +// Code generated by ent, DO NOT EDIT. + +package locality + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +// ID filters vertices based on their ID field. +func ID(id uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldLTE(FieldID, id)) +} + +// Slug applies equality check predicate on the "slug" field. It's identical to SlugEQ. +func Slug(v string) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldSlug, v)) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldName, v)) +} + +// ParentParishID applies equality check predicate on the "parent_parish_id" field. It's identical to ParentParishIDEQ. +func ParentParishID(v uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldParentParishID, v)) +} + +// Source applies equality check predicate on the "source" field. It's identical to SourceEQ. +func Source(v string) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldSource, v)) +} + +// SourceElementID applies equality check predicate on the "source_element_id" field. It's identical to SourceElementIDEQ. +func SourceElementID(v string) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldSourceElementID, v)) +} + +// SourceVersion applies equality check predicate on the "source_version" field. It's identical to SourceVersionEQ. +func SourceVersion(v string) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldSourceVersion, v)) +} + +// SourceRetrievedAt applies equality check predicate on the "source_retrieved_at" field. It's identical to SourceRetrievedAtEQ. +func SourceRetrievedAt(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldSourceRetrievedAt, v)) +} + +// Latitude applies equality check predicate on the "latitude" field. It's identical to LatitudeEQ. +func Latitude(v float64) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldLatitude, v)) +} + +// Longitude applies equality check predicate on the "longitude" field. It's identical to LongitudeEQ. +func Longitude(v float64) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldLongitude, v)) +} + +// Active applies equality check predicate on the "active" field. It's identical to ActiveEQ. +func Active(v bool) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldActive, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// SlugEQ applies the EQ predicate on the "slug" field. +func SlugEQ(v string) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldSlug, v)) +} + +// SlugNEQ applies the NEQ predicate on the "slug" field. +func SlugNEQ(v string) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldSlug, v)) +} + +// SlugIn applies the In predicate on the "slug" field. +func SlugIn(vs ...string) predicate.Locality { + return predicate.Locality(sql.FieldIn(FieldSlug, vs...)) +} + +// SlugNotIn applies the NotIn predicate on the "slug" field. +func SlugNotIn(vs ...string) predicate.Locality { + return predicate.Locality(sql.FieldNotIn(FieldSlug, vs...)) +} + +// SlugGT applies the GT predicate on the "slug" field. +func SlugGT(v string) predicate.Locality { + return predicate.Locality(sql.FieldGT(FieldSlug, v)) +} + +// SlugGTE applies the GTE predicate on the "slug" field. +func SlugGTE(v string) predicate.Locality { + return predicate.Locality(sql.FieldGTE(FieldSlug, v)) +} + +// SlugLT applies the LT predicate on the "slug" field. +func SlugLT(v string) predicate.Locality { + return predicate.Locality(sql.FieldLT(FieldSlug, v)) +} + +// SlugLTE applies the LTE predicate on the "slug" field. +func SlugLTE(v string) predicate.Locality { + return predicate.Locality(sql.FieldLTE(FieldSlug, v)) +} + +// SlugContains applies the Contains predicate on the "slug" field. +func SlugContains(v string) predicate.Locality { + return predicate.Locality(sql.FieldContains(FieldSlug, v)) +} + +// SlugHasPrefix applies the HasPrefix predicate on the "slug" field. +func SlugHasPrefix(v string) predicate.Locality { + return predicate.Locality(sql.FieldHasPrefix(FieldSlug, v)) +} + +// SlugHasSuffix applies the HasSuffix predicate on the "slug" field. +func SlugHasSuffix(v string) predicate.Locality { + return predicate.Locality(sql.FieldHasSuffix(FieldSlug, v)) +} + +// SlugEqualFold applies the EqualFold predicate on the "slug" field. +func SlugEqualFold(v string) predicate.Locality { + return predicate.Locality(sql.FieldEqualFold(FieldSlug, v)) +} + +// SlugContainsFold applies the ContainsFold predicate on the "slug" field. +func SlugContainsFold(v string) predicate.Locality { + return predicate.Locality(sql.FieldContainsFold(FieldSlug, v)) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldName, v)) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldName, v)) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.Locality { + return predicate.Locality(sql.FieldIn(FieldName, vs...)) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.Locality { + return predicate.Locality(sql.FieldNotIn(FieldName, vs...)) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.Locality { + return predicate.Locality(sql.FieldGT(FieldName, v)) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.Locality { + return predicate.Locality(sql.FieldGTE(FieldName, v)) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.Locality { + return predicate.Locality(sql.FieldLT(FieldName, v)) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.Locality { + return predicate.Locality(sql.FieldLTE(FieldName, v)) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.Locality { + return predicate.Locality(sql.FieldContains(FieldName, v)) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.Locality { + return predicate.Locality(sql.FieldHasPrefix(FieldName, v)) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.Locality { + return predicate.Locality(sql.FieldHasSuffix(FieldName, v)) +} + +// NameEqualFold applies the EqualFold predicate on the "name" field. +func NameEqualFold(v string) predicate.Locality { + return predicate.Locality(sql.FieldEqualFold(FieldName, v)) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.Locality { + return predicate.Locality(sql.FieldContainsFold(FieldName, v)) +} + +// ParentParishIDEQ applies the EQ predicate on the "parent_parish_id" field. +func ParentParishIDEQ(v uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldParentParishID, v)) +} + +// ParentParishIDNEQ applies the NEQ predicate on the "parent_parish_id" field. +func ParentParishIDNEQ(v uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldParentParishID, v)) +} + +// ParentParishIDIn applies the In predicate on the "parent_parish_id" field. +func ParentParishIDIn(vs ...uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldIn(FieldParentParishID, vs...)) +} + +// ParentParishIDNotIn applies the NotIn predicate on the "parent_parish_id" field. +func ParentParishIDNotIn(vs ...uuid.UUID) predicate.Locality { + return predicate.Locality(sql.FieldNotIn(FieldParentParishID, vs...)) +} + +// SourceEQ applies the EQ predicate on the "source" field. +func SourceEQ(v string) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldSource, v)) +} + +// SourceNEQ applies the NEQ predicate on the "source" field. +func SourceNEQ(v string) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldSource, v)) +} + +// SourceIn applies the In predicate on the "source" field. +func SourceIn(vs ...string) predicate.Locality { + return predicate.Locality(sql.FieldIn(FieldSource, vs...)) +} + +// SourceNotIn applies the NotIn predicate on the "source" field. +func SourceNotIn(vs ...string) predicate.Locality { + return predicate.Locality(sql.FieldNotIn(FieldSource, vs...)) +} + +// SourceGT applies the GT predicate on the "source" field. +func SourceGT(v string) predicate.Locality { + return predicate.Locality(sql.FieldGT(FieldSource, v)) +} + +// SourceGTE applies the GTE predicate on the "source" field. +func SourceGTE(v string) predicate.Locality { + return predicate.Locality(sql.FieldGTE(FieldSource, v)) +} + +// SourceLT applies the LT predicate on the "source" field. +func SourceLT(v string) predicate.Locality { + return predicate.Locality(sql.FieldLT(FieldSource, v)) +} + +// SourceLTE applies the LTE predicate on the "source" field. +func SourceLTE(v string) predicate.Locality { + return predicate.Locality(sql.FieldLTE(FieldSource, v)) +} + +// SourceContains applies the Contains predicate on the "source" field. +func SourceContains(v string) predicate.Locality { + return predicate.Locality(sql.FieldContains(FieldSource, v)) +} + +// SourceHasPrefix applies the HasPrefix predicate on the "source" field. +func SourceHasPrefix(v string) predicate.Locality { + return predicate.Locality(sql.FieldHasPrefix(FieldSource, v)) +} + +// SourceHasSuffix applies the HasSuffix predicate on the "source" field. +func SourceHasSuffix(v string) predicate.Locality { + return predicate.Locality(sql.FieldHasSuffix(FieldSource, v)) +} + +// SourceEqualFold applies the EqualFold predicate on the "source" field. +func SourceEqualFold(v string) predicate.Locality { + return predicate.Locality(sql.FieldEqualFold(FieldSource, v)) +} + +// SourceContainsFold applies the ContainsFold predicate on the "source" field. +func SourceContainsFold(v string) predicate.Locality { + return predicate.Locality(sql.FieldContainsFold(FieldSource, v)) +} + +// SourceElementIDEQ applies the EQ predicate on the "source_element_id" field. +func SourceElementIDEQ(v string) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldSourceElementID, v)) +} + +// SourceElementIDNEQ applies the NEQ predicate on the "source_element_id" field. +func SourceElementIDNEQ(v string) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldSourceElementID, v)) +} + +// SourceElementIDIn applies the In predicate on the "source_element_id" field. +func SourceElementIDIn(vs ...string) predicate.Locality { + return predicate.Locality(sql.FieldIn(FieldSourceElementID, vs...)) +} + +// SourceElementIDNotIn applies the NotIn predicate on the "source_element_id" field. +func SourceElementIDNotIn(vs ...string) predicate.Locality { + return predicate.Locality(sql.FieldNotIn(FieldSourceElementID, vs...)) +} + +// SourceElementIDGT applies the GT predicate on the "source_element_id" field. +func SourceElementIDGT(v string) predicate.Locality { + return predicate.Locality(sql.FieldGT(FieldSourceElementID, v)) +} + +// SourceElementIDGTE applies the GTE predicate on the "source_element_id" field. +func SourceElementIDGTE(v string) predicate.Locality { + return predicate.Locality(sql.FieldGTE(FieldSourceElementID, v)) +} + +// SourceElementIDLT applies the LT predicate on the "source_element_id" field. +func SourceElementIDLT(v string) predicate.Locality { + return predicate.Locality(sql.FieldLT(FieldSourceElementID, v)) +} + +// SourceElementIDLTE applies the LTE predicate on the "source_element_id" field. +func SourceElementIDLTE(v string) predicate.Locality { + return predicate.Locality(sql.FieldLTE(FieldSourceElementID, v)) +} + +// SourceElementIDContains applies the Contains predicate on the "source_element_id" field. +func SourceElementIDContains(v string) predicate.Locality { + return predicate.Locality(sql.FieldContains(FieldSourceElementID, v)) +} + +// SourceElementIDHasPrefix applies the HasPrefix predicate on the "source_element_id" field. +func SourceElementIDHasPrefix(v string) predicate.Locality { + return predicate.Locality(sql.FieldHasPrefix(FieldSourceElementID, v)) +} + +// SourceElementIDHasSuffix applies the HasSuffix predicate on the "source_element_id" field. +func SourceElementIDHasSuffix(v string) predicate.Locality { + return predicate.Locality(sql.FieldHasSuffix(FieldSourceElementID, v)) +} + +// SourceElementIDEqualFold applies the EqualFold predicate on the "source_element_id" field. +func SourceElementIDEqualFold(v string) predicate.Locality { + return predicate.Locality(sql.FieldEqualFold(FieldSourceElementID, v)) +} + +// SourceElementIDContainsFold applies the ContainsFold predicate on the "source_element_id" field. +func SourceElementIDContainsFold(v string) predicate.Locality { + return predicate.Locality(sql.FieldContainsFold(FieldSourceElementID, v)) +} + +// SourceVersionEQ applies the EQ predicate on the "source_version" field. +func SourceVersionEQ(v string) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldSourceVersion, v)) +} + +// SourceVersionNEQ applies the NEQ predicate on the "source_version" field. +func SourceVersionNEQ(v string) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldSourceVersion, v)) +} + +// SourceVersionIn applies the In predicate on the "source_version" field. +func SourceVersionIn(vs ...string) predicate.Locality { + return predicate.Locality(sql.FieldIn(FieldSourceVersion, vs...)) +} + +// SourceVersionNotIn applies the NotIn predicate on the "source_version" field. +func SourceVersionNotIn(vs ...string) predicate.Locality { + return predicate.Locality(sql.FieldNotIn(FieldSourceVersion, vs...)) +} + +// SourceVersionGT applies the GT predicate on the "source_version" field. +func SourceVersionGT(v string) predicate.Locality { + return predicate.Locality(sql.FieldGT(FieldSourceVersion, v)) +} + +// SourceVersionGTE applies the GTE predicate on the "source_version" field. +func SourceVersionGTE(v string) predicate.Locality { + return predicate.Locality(sql.FieldGTE(FieldSourceVersion, v)) +} + +// SourceVersionLT applies the LT predicate on the "source_version" field. +func SourceVersionLT(v string) predicate.Locality { + return predicate.Locality(sql.FieldLT(FieldSourceVersion, v)) +} + +// SourceVersionLTE applies the LTE predicate on the "source_version" field. +func SourceVersionLTE(v string) predicate.Locality { + return predicate.Locality(sql.FieldLTE(FieldSourceVersion, v)) +} + +// SourceVersionContains applies the Contains predicate on the "source_version" field. +func SourceVersionContains(v string) predicate.Locality { + return predicate.Locality(sql.FieldContains(FieldSourceVersion, v)) +} + +// SourceVersionHasPrefix applies the HasPrefix predicate on the "source_version" field. +func SourceVersionHasPrefix(v string) predicate.Locality { + return predicate.Locality(sql.FieldHasPrefix(FieldSourceVersion, v)) +} + +// SourceVersionHasSuffix applies the HasSuffix predicate on the "source_version" field. +func SourceVersionHasSuffix(v string) predicate.Locality { + return predicate.Locality(sql.FieldHasSuffix(FieldSourceVersion, v)) +} + +// SourceVersionEqualFold applies the EqualFold predicate on the "source_version" field. +func SourceVersionEqualFold(v string) predicate.Locality { + return predicate.Locality(sql.FieldEqualFold(FieldSourceVersion, v)) +} + +// SourceVersionContainsFold applies the ContainsFold predicate on the "source_version" field. +func SourceVersionContainsFold(v string) predicate.Locality { + return predicate.Locality(sql.FieldContainsFold(FieldSourceVersion, v)) +} + +// SourceRetrievedAtEQ applies the EQ predicate on the "source_retrieved_at" field. +func SourceRetrievedAtEQ(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldSourceRetrievedAt, v)) +} + +// SourceRetrievedAtNEQ applies the NEQ predicate on the "source_retrieved_at" field. +func SourceRetrievedAtNEQ(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldSourceRetrievedAt, v)) +} + +// SourceRetrievedAtIn applies the In predicate on the "source_retrieved_at" field. +func SourceRetrievedAtIn(vs ...time.Time) predicate.Locality { + return predicate.Locality(sql.FieldIn(FieldSourceRetrievedAt, vs...)) +} + +// SourceRetrievedAtNotIn applies the NotIn predicate on the "source_retrieved_at" field. +func SourceRetrievedAtNotIn(vs ...time.Time) predicate.Locality { + return predicate.Locality(sql.FieldNotIn(FieldSourceRetrievedAt, vs...)) +} + +// SourceRetrievedAtGT applies the GT predicate on the "source_retrieved_at" field. +func SourceRetrievedAtGT(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldGT(FieldSourceRetrievedAt, v)) +} + +// SourceRetrievedAtGTE applies the GTE predicate on the "source_retrieved_at" field. +func SourceRetrievedAtGTE(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldGTE(FieldSourceRetrievedAt, v)) +} + +// SourceRetrievedAtLT applies the LT predicate on the "source_retrieved_at" field. +func SourceRetrievedAtLT(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldLT(FieldSourceRetrievedAt, v)) +} + +// SourceRetrievedAtLTE applies the LTE predicate on the "source_retrieved_at" field. +func SourceRetrievedAtLTE(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldLTE(FieldSourceRetrievedAt, v)) +} + +// LatitudeEQ applies the EQ predicate on the "latitude" field. +func LatitudeEQ(v float64) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldLatitude, v)) +} + +// LatitudeNEQ applies the NEQ predicate on the "latitude" field. +func LatitudeNEQ(v float64) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldLatitude, v)) +} + +// LatitudeIn applies the In predicate on the "latitude" field. +func LatitudeIn(vs ...float64) predicate.Locality { + return predicate.Locality(sql.FieldIn(FieldLatitude, vs...)) +} + +// LatitudeNotIn applies the NotIn predicate on the "latitude" field. +func LatitudeNotIn(vs ...float64) predicate.Locality { + return predicate.Locality(sql.FieldNotIn(FieldLatitude, vs...)) +} + +// LatitudeGT applies the GT predicate on the "latitude" field. +func LatitudeGT(v float64) predicate.Locality { + return predicate.Locality(sql.FieldGT(FieldLatitude, v)) +} + +// LatitudeGTE applies the GTE predicate on the "latitude" field. +func LatitudeGTE(v float64) predicate.Locality { + return predicate.Locality(sql.FieldGTE(FieldLatitude, v)) +} + +// LatitudeLT applies the LT predicate on the "latitude" field. +func LatitudeLT(v float64) predicate.Locality { + return predicate.Locality(sql.FieldLT(FieldLatitude, v)) +} + +// LatitudeLTE applies the LTE predicate on the "latitude" field. +func LatitudeLTE(v float64) predicate.Locality { + return predicate.Locality(sql.FieldLTE(FieldLatitude, v)) +} + +// LongitudeEQ applies the EQ predicate on the "longitude" field. +func LongitudeEQ(v float64) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldLongitude, v)) +} + +// LongitudeNEQ applies the NEQ predicate on the "longitude" field. +func LongitudeNEQ(v float64) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldLongitude, v)) +} + +// LongitudeIn applies the In predicate on the "longitude" field. +func LongitudeIn(vs ...float64) predicate.Locality { + return predicate.Locality(sql.FieldIn(FieldLongitude, vs...)) +} + +// LongitudeNotIn applies the NotIn predicate on the "longitude" field. +func LongitudeNotIn(vs ...float64) predicate.Locality { + return predicate.Locality(sql.FieldNotIn(FieldLongitude, vs...)) +} + +// LongitudeGT applies the GT predicate on the "longitude" field. +func LongitudeGT(v float64) predicate.Locality { + return predicate.Locality(sql.FieldGT(FieldLongitude, v)) +} + +// LongitudeGTE applies the GTE predicate on the "longitude" field. +func LongitudeGTE(v float64) predicate.Locality { + return predicate.Locality(sql.FieldGTE(FieldLongitude, v)) +} + +// LongitudeLT applies the LT predicate on the "longitude" field. +func LongitudeLT(v float64) predicate.Locality { + return predicate.Locality(sql.FieldLT(FieldLongitude, v)) +} + +// LongitudeLTE applies the LTE predicate on the "longitude" field. +func LongitudeLTE(v float64) predicate.Locality { + return predicate.Locality(sql.FieldLTE(FieldLongitude, v)) +} + +// ActiveEQ applies the EQ predicate on the "active" field. +func ActiveEQ(v bool) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldActive, v)) +} + +// ActiveNEQ applies the NEQ predicate on the "active" field. +func ActiveNEQ(v bool) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldActive, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.Locality { + return predicate.Locality(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.Locality { + return predicate.Locality(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.Locality { + return predicate.Locality(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.Locality { + return predicate.Locality(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.Locality { + return predicate.Locality(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// HasParentParish applies the HasEdge predicate on the "parent_parish" edge. +func HasParentParish() predicate.Locality { + return predicate.Locality(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, ParentParishTable, ParentParishColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasParentParishWith applies the HasEdge predicate on the "parent_parish" edge with a given conditions (other predicates). +func HasParentParishWith(preds ...predicate.AdministrativeArea) predicate.Locality { + return predicate.Locality(func(s *sql.Selector) { + step := newParentParishStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasProviderProfiles applies the HasEdge predicate on the "provider_profiles" edge. +func HasProviderProfiles() predicate.Locality { + return predicate.Locality(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, ProviderProfilesTable, ProviderProfilesPrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasProviderProfilesWith applies the HasEdge predicate on the "provider_profiles" edge with a given conditions (other predicates). +func HasProviderProfilesWith(preds ...predicate.ProviderProfile) predicate.Locality { + return predicate.Locality(func(s *sql.Selector) { + step := newProviderProfilesStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.Locality) predicate.Locality { + return predicate.Locality(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.Locality) predicate.Locality { + return predicate.Locality(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.Locality) predicate.Locality { + return predicate.Locality(sql.NotPredicates(p)) +} diff --git a/backend/ent/locality_create.go b/backend/ent/locality_create.go new file mode 100644 index 0000000..b14fe6c --- /dev/null +++ b/backend/ent/locality_create.go @@ -0,0 +1,482 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/administrativearea" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/google/uuid" +) + +// LocalityCreate is the builder for creating a Locality entity. +type LocalityCreate struct { + config + mutation *LocalityMutation + hooks []Hook +} + +// SetSlug sets the "slug" field. +func (_c *LocalityCreate) SetSlug(v string) *LocalityCreate { + _c.mutation.SetSlug(v) + return _c +} + +// SetName sets the "name" field. +func (_c *LocalityCreate) SetName(v string) *LocalityCreate { + _c.mutation.SetName(v) + return _c +} + +// SetParentParishID sets the "parent_parish_id" field. +func (_c *LocalityCreate) SetParentParishID(v uuid.UUID) *LocalityCreate { + _c.mutation.SetParentParishID(v) + return _c +} + +// SetSource sets the "source" field. +func (_c *LocalityCreate) SetSource(v string) *LocalityCreate { + _c.mutation.SetSource(v) + return _c +} + +// SetSourceElementID sets the "source_element_id" field. +func (_c *LocalityCreate) SetSourceElementID(v string) *LocalityCreate { + _c.mutation.SetSourceElementID(v) + return _c +} + +// SetSourceVersion sets the "source_version" field. +func (_c *LocalityCreate) SetSourceVersion(v string) *LocalityCreate { + _c.mutation.SetSourceVersion(v) + return _c +} + +// SetSourceRetrievedAt sets the "source_retrieved_at" field. +func (_c *LocalityCreate) SetSourceRetrievedAt(v time.Time) *LocalityCreate { + _c.mutation.SetSourceRetrievedAt(v) + return _c +} + +// SetLatitude sets the "latitude" field. +func (_c *LocalityCreate) SetLatitude(v float64) *LocalityCreate { + _c.mutation.SetLatitude(v) + return _c +} + +// SetLongitude sets the "longitude" field. +func (_c *LocalityCreate) SetLongitude(v float64) *LocalityCreate { + _c.mutation.SetLongitude(v) + return _c +} + +// SetActive sets the "active" field. +func (_c *LocalityCreate) SetActive(v bool) *LocalityCreate { + _c.mutation.SetActive(v) + return _c +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_c *LocalityCreate) SetNillableActive(v *bool) *LocalityCreate { + if v != nil { + _c.SetActive(*v) + } + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *LocalityCreate) SetCreatedAt(v time.Time) *LocalityCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *LocalityCreate) SetNillableCreatedAt(v *time.Time) *LocalityCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *LocalityCreate) SetUpdatedAt(v time.Time) *LocalityCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (_c *LocalityCreate) SetNillableUpdatedAt(v *time.Time) *LocalityCreate { + if v != nil { + _c.SetUpdatedAt(*v) + } + return _c +} + +// SetID sets the "id" field. +func (_c *LocalityCreate) SetID(v uuid.UUID) *LocalityCreate { + _c.mutation.SetID(v) + return _c +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (_c *LocalityCreate) SetNillableID(v *uuid.UUID) *LocalityCreate { + if v != nil { + _c.SetID(*v) + } + return _c +} + +// SetParentParish sets the "parent_parish" edge to the AdministrativeArea entity. +func (_c *LocalityCreate) SetParentParish(v *AdministrativeArea) *LocalityCreate { + return _c.SetParentParishID(v.ID) +} + +// AddProviderProfileIDs adds the "provider_profiles" edge to the ProviderProfile entity by IDs. +func (_c *LocalityCreate) AddProviderProfileIDs(ids ...uuid.UUID) *LocalityCreate { + _c.mutation.AddProviderProfileIDs(ids...) + return _c +} + +// AddProviderProfiles adds the "provider_profiles" edges to the ProviderProfile entity. +func (_c *LocalityCreate) AddProviderProfiles(v ...*ProviderProfile) *LocalityCreate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddProviderProfileIDs(ids...) +} + +// Mutation returns the LocalityMutation object of the builder. +func (_c *LocalityCreate) Mutation() *LocalityMutation { + return _c.mutation +} + +// Save creates the Locality in the database. +func (_c *LocalityCreate) Save(ctx context.Context) (*Locality, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *LocalityCreate) SaveX(ctx context.Context) *Locality { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *LocalityCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *LocalityCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *LocalityCreate) defaults() { + if _, ok := _c.mutation.Active(); !ok { + v := locality.DefaultActive + _c.mutation.SetActive(v) + } + if _, ok := _c.mutation.CreatedAt(); !ok { + v := locality.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + v := locality.DefaultUpdatedAt() + _c.mutation.SetUpdatedAt(v) + } + if _, ok := _c.mutation.ID(); !ok { + v := locality.DefaultID() + _c.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *LocalityCreate) check() error { + if _, ok := _c.mutation.Slug(); !ok { + return &ValidationError{Name: "slug", err: errors.New(`ent: missing required field "Locality.slug"`)} + } + if v, ok := _c.mutation.Slug(); ok { + if err := locality.SlugValidator(v); err != nil { + return &ValidationError{Name: "slug", err: fmt.Errorf(`ent: validator failed for field "Locality.slug": %w`, err)} + } + } + if _, ok := _c.mutation.Name(); !ok { + return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "Locality.name"`)} + } + if v, ok := _c.mutation.Name(); ok { + if err := locality.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Locality.name": %w`, err)} + } + } + if _, ok := _c.mutation.ParentParishID(); !ok { + return &ValidationError{Name: "parent_parish_id", err: errors.New(`ent: missing required field "Locality.parent_parish_id"`)} + } + if _, ok := _c.mutation.Source(); !ok { + return &ValidationError{Name: "source", err: errors.New(`ent: missing required field "Locality.source"`)} + } + if v, ok := _c.mutation.Source(); ok { + if err := locality.SourceValidator(v); err != nil { + return &ValidationError{Name: "source", err: fmt.Errorf(`ent: validator failed for field "Locality.source": %w`, err)} + } + } + if _, ok := _c.mutation.SourceElementID(); !ok { + return &ValidationError{Name: "source_element_id", err: errors.New(`ent: missing required field "Locality.source_element_id"`)} + } + if v, ok := _c.mutation.SourceElementID(); ok { + if err := locality.SourceElementIDValidator(v); err != nil { + return &ValidationError{Name: "source_element_id", err: fmt.Errorf(`ent: validator failed for field "Locality.source_element_id": %w`, err)} + } + } + if _, ok := _c.mutation.SourceVersion(); !ok { + return &ValidationError{Name: "source_version", err: errors.New(`ent: missing required field "Locality.source_version"`)} + } + if v, ok := _c.mutation.SourceVersion(); ok { + if err := locality.SourceVersionValidator(v); err != nil { + return &ValidationError{Name: "source_version", err: fmt.Errorf(`ent: validator failed for field "Locality.source_version": %w`, err)} + } + } + if _, ok := _c.mutation.SourceRetrievedAt(); !ok { + return &ValidationError{Name: "source_retrieved_at", err: errors.New(`ent: missing required field "Locality.source_retrieved_at"`)} + } + if _, ok := _c.mutation.Latitude(); !ok { + return &ValidationError{Name: "latitude", err: errors.New(`ent: missing required field "Locality.latitude"`)} + } + if v, ok := _c.mutation.Latitude(); ok { + if err := locality.LatitudeValidator(v); err != nil { + return &ValidationError{Name: "latitude", err: fmt.Errorf(`ent: validator failed for field "Locality.latitude": %w`, err)} + } + } + if _, ok := _c.mutation.Longitude(); !ok { + return &ValidationError{Name: "longitude", err: errors.New(`ent: missing required field "Locality.longitude"`)} + } + if v, ok := _c.mutation.Longitude(); ok { + if err := locality.LongitudeValidator(v); err != nil { + return &ValidationError{Name: "longitude", err: fmt.Errorf(`ent: validator failed for field "Locality.longitude": %w`, err)} + } + } + if _, ok := _c.mutation.Active(); !ok { + return &ValidationError{Name: "active", err: errors.New(`ent: missing required field "Locality.active"`)} + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Locality.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "Locality.updated_at"`)} + } + if len(_c.mutation.ParentParishIDs()) == 0 { + return &ValidationError{Name: "parent_parish", err: errors.New(`ent: missing required edge "Locality.parent_parish"`)} + } + return nil +} + +func (_c *LocalityCreate) sqlSave(ctx context.Context) (*Locality, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(*uuid.UUID); ok { + _node.ID = *id + } else if err := _node.ID.Scan(_spec.ID.Value); err != nil { + return nil, err + } + } + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *LocalityCreate) createSpec() (*Locality, *sqlgraph.CreateSpec) { + var ( + _node = &Locality{config: _c.config} + _spec = sqlgraph.NewCreateSpec(locality.Table, sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID)) + ) + if id, ok := _c.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = &id + } + if value, ok := _c.mutation.Slug(); ok { + _spec.SetField(locality.FieldSlug, field.TypeString, value) + _node.Slug = value + } + if value, ok := _c.mutation.Name(); ok { + _spec.SetField(locality.FieldName, field.TypeString, value) + _node.Name = value + } + if value, ok := _c.mutation.Source(); ok { + _spec.SetField(locality.FieldSource, field.TypeString, value) + _node.Source = value + } + if value, ok := _c.mutation.SourceElementID(); ok { + _spec.SetField(locality.FieldSourceElementID, field.TypeString, value) + _node.SourceElementID = value + } + if value, ok := _c.mutation.SourceVersion(); ok { + _spec.SetField(locality.FieldSourceVersion, field.TypeString, value) + _node.SourceVersion = value + } + if value, ok := _c.mutation.SourceRetrievedAt(); ok { + _spec.SetField(locality.FieldSourceRetrievedAt, field.TypeTime, value) + _node.SourceRetrievedAt = value + } + if value, ok := _c.mutation.Latitude(); ok { + _spec.SetField(locality.FieldLatitude, field.TypeFloat64, value) + _node.Latitude = value + } + if value, ok := _c.mutation.Longitude(); ok { + _spec.SetField(locality.FieldLongitude, field.TypeFloat64, value) + _node.Longitude = value + } + if value, ok := _c.mutation.Active(); ok { + _spec.SetField(locality.FieldActive, field.TypeBool, value) + _node.Active = value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(locality.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(locality.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if nodes := _c.mutation.ParentParishIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: locality.ParentParishTable, + Columns: []string{locality.ParentParishColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(administrativearea.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.ParentParishID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.ProviderProfilesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: locality.ProviderProfilesTable, + Columns: locality.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// LocalityCreateBulk is the builder for creating many Locality entities in bulk. +type LocalityCreateBulk struct { + config + err error + builders []*LocalityCreate +} + +// Save creates the Locality entities in the database. +func (_c *LocalityCreateBulk) Save(ctx context.Context) ([]*Locality, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*Locality, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*LocalityMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *LocalityCreateBulk) SaveX(ctx context.Context) []*Locality { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *LocalityCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *LocalityCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/locality_delete.go b/backend/ent/locality_delete.go new file mode 100644 index 0000000..5478bc6 --- /dev/null +++ b/backend/ent/locality_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" +) + +// LocalityDelete is the builder for deleting a Locality entity. +type LocalityDelete struct { + config + hooks []Hook + mutation *LocalityMutation +} + +// Where appends a list predicates to the LocalityDelete builder. +func (_d *LocalityDelete) Where(ps ...predicate.Locality) *LocalityDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *LocalityDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *LocalityDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *LocalityDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(locality.Table, sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// LocalityDeleteOne is the builder for deleting a single Locality entity. +type LocalityDeleteOne struct { + _d *LocalityDelete +} + +// Where appends a list predicates to the LocalityDelete builder. +func (_d *LocalityDeleteOne) Where(ps ...predicate.Locality) *LocalityDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *LocalityDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{locality.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *LocalityDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/locality_query.go b/backend/ent/locality_query.go new file mode 100644 index 0000000..5a3896c --- /dev/null +++ b/backend/ent/locality_query.go @@ -0,0 +1,713 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "database/sql/driver" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/administrativearea" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/google/uuid" +) + +// LocalityQuery is the builder for querying Locality entities. +type LocalityQuery struct { + config + ctx *QueryContext + order []locality.OrderOption + inters []Interceptor + predicates []predicate.Locality + withParentParish *AdministrativeAreaQuery + withProviderProfiles *ProviderProfileQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the LocalityQuery builder. +func (_q *LocalityQuery) Where(ps ...predicate.Locality) *LocalityQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *LocalityQuery) Limit(limit int) *LocalityQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *LocalityQuery) Offset(offset int) *LocalityQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *LocalityQuery) Unique(unique bool) *LocalityQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *LocalityQuery) Order(o ...locality.OrderOption) *LocalityQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QueryParentParish chains the current query on the "parent_parish" edge. +func (_q *LocalityQuery) QueryParentParish() *AdministrativeAreaQuery { + query := (&AdministrativeAreaClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(locality.Table, locality.FieldID, selector), + sqlgraph.To(administrativearea.Table, administrativearea.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, locality.ParentParishTable, locality.ParentParishColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryProviderProfiles chains the current query on the "provider_profiles" edge. +func (_q *LocalityQuery) QueryProviderProfiles() *ProviderProfileQuery { + query := (&ProviderProfileClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(locality.Table, locality.FieldID, selector), + sqlgraph.To(providerprofile.Table, providerprofile.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, locality.ProviderProfilesTable, locality.ProviderProfilesPrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first Locality entity from the query. +// Returns a *NotFoundError when no Locality was found. +func (_q *LocalityQuery) First(ctx context.Context) (*Locality, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{locality.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *LocalityQuery) FirstX(ctx context.Context) *Locality { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first Locality ID from the query. +// Returns a *NotFoundError when no Locality ID was found. +func (_q *LocalityQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{locality.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *LocalityQuery) FirstIDX(ctx context.Context) uuid.UUID { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single Locality entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one Locality entity is found. +// Returns a *NotFoundError when no Locality entities are found. +func (_q *LocalityQuery) Only(ctx context.Context) (*Locality, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{locality.Label} + default: + return nil, &NotSingularError{locality.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *LocalityQuery) OnlyX(ctx context.Context) *Locality { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only Locality ID in the query. +// Returns a *NotSingularError when more than one Locality ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *LocalityQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{locality.Label} + default: + err = &NotSingularError{locality.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *LocalityQuery) OnlyIDX(ctx context.Context) uuid.UUID { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Localities. +func (_q *LocalityQuery) All(ctx context.Context) ([]*Locality, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*Locality, *LocalityQuery]() + return withInterceptors[[]*Locality](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *LocalityQuery) AllX(ctx context.Context) []*Locality { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of Locality IDs. +func (_q *LocalityQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(locality.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *LocalityQuery) IDsX(ctx context.Context) []uuid.UUID { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *LocalityQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*LocalityQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *LocalityQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *LocalityQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *LocalityQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the LocalityQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *LocalityQuery) Clone() *LocalityQuery { + if _q == nil { + return nil + } + return &LocalityQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]locality.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.Locality{}, _q.predicates...), + withParentParish: _q.withParentParish.Clone(), + withProviderProfiles: _q.withProviderProfiles.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// WithParentParish tells the query-builder to eager-load the nodes that are connected to +// the "parent_parish" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *LocalityQuery) WithParentParish(opts ...func(*AdministrativeAreaQuery)) *LocalityQuery { + query := (&AdministrativeAreaClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withParentParish = query + return _q +} + +// WithProviderProfiles tells the query-builder to eager-load the nodes that are connected to +// the "provider_profiles" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *LocalityQuery) WithProviderProfiles(opts ...func(*ProviderProfileQuery)) *LocalityQuery { + query := (&ProviderProfileClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withProviderProfiles = query + return _q +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// Slug string `json:"slug,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.Locality.Query(). +// GroupBy(locality.FieldSlug). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *LocalityQuery) GroupBy(field string, fields ...string) *LocalityGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &LocalityGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = locality.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// Slug string `json:"slug,omitempty"` +// } +// +// client.Locality.Query(). +// Select(locality.FieldSlug). +// Scan(ctx, &v) +func (_q *LocalityQuery) Select(fields ...string) *LocalitySelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &LocalitySelect{LocalityQuery: _q} + sbuild.label = locality.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a LocalitySelect configured with the given aggregations. +func (_q *LocalityQuery) Aggregate(fns ...AggregateFunc) *LocalitySelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *LocalityQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !locality.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *LocalityQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Locality, error) { + var ( + nodes = []*Locality{} + _spec = _q.querySpec() + loadedTypes = [2]bool{ + _q.withParentParish != nil, + _q.withProviderProfiles != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*Locality).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &Locality{config: _q.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := _q.withParentParish; query != nil { + if err := _q.loadParentParish(ctx, query, nodes, nil, + func(n *Locality, e *AdministrativeArea) { n.Edges.ParentParish = e }); err != nil { + return nil, err + } + } + if query := _q.withProviderProfiles; query != nil { + if err := _q.loadProviderProfiles(ctx, query, nodes, + func(n *Locality) { n.Edges.ProviderProfiles = []*ProviderProfile{} }, + func(n *Locality, e *ProviderProfile) { n.Edges.ProviderProfiles = append(n.Edges.ProviderProfiles, e) }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *LocalityQuery) loadParentParish(ctx context.Context, query *AdministrativeAreaQuery, nodes []*Locality, init func(*Locality), assign func(*Locality, *AdministrativeArea)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*Locality) + for i := range nodes { + fk := nodes[i].ParentParishID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(administrativearea.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "parent_parish_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (_q *LocalityQuery) loadProviderProfiles(ctx context.Context, query *ProviderProfileQuery, nodes []*Locality, init func(*Locality), assign func(*Locality, *ProviderProfile)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[uuid.UUID]*Locality) + nids := make(map[uuid.UUID]map[*Locality]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(locality.ProviderProfilesTable) + s.Join(joinT).On(s.C(providerprofile.FieldID), joinT.C(locality.ProviderProfilesPrimaryKey[0])) + s.Where(sql.InValues(joinT.C(locality.ProviderProfilesPrimaryKey[1]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(locality.ProviderProfilesPrimaryKey[1])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(uuid.UUID)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := *values[0].(*uuid.UUID) + inValue := *values[1].(*uuid.UUID) + if nids[inValue] == nil { + nids[inValue] = map[*Locality]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*ProviderProfile](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "provider_profiles" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} + +func (_q *LocalityQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *LocalityQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(locality.Table, locality.Columns, sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, locality.FieldID) + for i := range fields { + if fields[i] != locality.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if _q.withParentParish != nil { + _spec.Node.AddColumnOnce(locality.FieldParentParishID) + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *LocalityQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(locality.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = locality.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// LocalityGroupBy is the group-by builder for Locality entities. +type LocalityGroupBy struct { + selector + build *LocalityQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *LocalityGroupBy) Aggregate(fns ...AggregateFunc) *LocalityGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *LocalityGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*LocalityQuery, *LocalityGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *LocalityGroupBy) sqlScan(ctx context.Context, root *LocalityQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// LocalitySelect is the builder for selecting fields of Locality entities. +type LocalitySelect struct { + *LocalityQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *LocalitySelect) Aggregate(fns ...AggregateFunc) *LocalitySelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *LocalitySelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*LocalityQuery, *LocalitySelect](ctx, _s.LocalityQuery, _s, _s.inters, v) +} + +func (_s *LocalitySelect) sqlScan(ctx context.Context, root *LocalityQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/locality_update.go b/backend/ent/locality_update.go new file mode 100644 index 0000000..cff622e --- /dev/null +++ b/backend/ent/locality_update.go @@ -0,0 +1,476 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/google/uuid" +) + +// LocalityUpdate is the builder for updating Locality entities. +type LocalityUpdate struct { + config + hooks []Hook + mutation *LocalityMutation +} + +// Where appends a list predicates to the LocalityUpdate builder. +func (_u *LocalityUpdate) Where(ps ...predicate.Locality) *LocalityUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetName sets the "name" field. +func (_u *LocalityUpdate) SetName(v string) *LocalityUpdate { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *LocalityUpdate) SetNillableName(v *string) *LocalityUpdate { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetActive sets the "active" field. +func (_u *LocalityUpdate) SetActive(v bool) *LocalityUpdate { + _u.mutation.SetActive(v) + return _u +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_u *LocalityUpdate) SetNillableActive(v *bool) *LocalityUpdate { + if v != nil { + _u.SetActive(*v) + } + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *LocalityUpdate) SetUpdatedAt(v time.Time) *LocalityUpdate { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// AddProviderProfileIDs adds the "provider_profiles" edge to the ProviderProfile entity by IDs. +func (_u *LocalityUpdate) AddProviderProfileIDs(ids ...uuid.UUID) *LocalityUpdate { + _u.mutation.AddProviderProfileIDs(ids...) + return _u +} + +// AddProviderProfiles adds the "provider_profiles" edges to the ProviderProfile entity. +func (_u *LocalityUpdate) AddProviderProfiles(v ...*ProviderProfile) *LocalityUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddProviderProfileIDs(ids...) +} + +// Mutation returns the LocalityMutation object of the builder. +func (_u *LocalityUpdate) Mutation() *LocalityMutation { + return _u.mutation +} + +// ClearProviderProfiles clears all "provider_profiles" edges to the ProviderProfile entity. +func (_u *LocalityUpdate) ClearProviderProfiles() *LocalityUpdate { + _u.mutation.ClearProviderProfiles() + return _u +} + +// RemoveProviderProfileIDs removes the "provider_profiles" edge to ProviderProfile entities by IDs. +func (_u *LocalityUpdate) RemoveProviderProfileIDs(ids ...uuid.UUID) *LocalityUpdate { + _u.mutation.RemoveProviderProfileIDs(ids...) + return _u +} + +// RemoveProviderProfiles removes "provider_profiles" edges to ProviderProfile entities. +func (_u *LocalityUpdate) RemoveProviderProfiles(v ...*ProviderProfile) *LocalityUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveProviderProfileIDs(ids...) +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *LocalityUpdate) Save(ctx context.Context) (int, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *LocalityUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *LocalityUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *LocalityUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *LocalityUpdate) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := locality.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *LocalityUpdate) check() error { + if v, ok := _u.mutation.Name(); ok { + if err := locality.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Locality.name": %w`, err)} + } + } + if _u.mutation.ParentParishCleared() && len(_u.mutation.ParentParishIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "Locality.parent_parish"`) + } + return nil +} + +func (_u *LocalityUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(locality.Table, locality.Columns, sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(locality.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Active(); ok { + _spec.SetField(locality.FieldActive, field.TypeBool, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(locality.FieldUpdatedAt, field.TypeTime, value) + } + if _u.mutation.ProviderProfilesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: locality.ProviderProfilesTable, + Columns: locality.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedProviderProfilesIDs(); len(nodes) > 0 && !_u.mutation.ProviderProfilesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: locality.ProviderProfilesTable, + Columns: locality.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.ProviderProfilesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: locality.ProviderProfilesTable, + Columns: locality.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{locality.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// LocalityUpdateOne is the builder for updating a single Locality entity. +type LocalityUpdateOne struct { + config + fields []string + hooks []Hook + mutation *LocalityMutation +} + +// SetName sets the "name" field. +func (_u *LocalityUpdateOne) SetName(v string) *LocalityUpdateOne { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *LocalityUpdateOne) SetNillableName(v *string) *LocalityUpdateOne { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetActive sets the "active" field. +func (_u *LocalityUpdateOne) SetActive(v bool) *LocalityUpdateOne { + _u.mutation.SetActive(v) + return _u +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_u *LocalityUpdateOne) SetNillableActive(v *bool) *LocalityUpdateOne { + if v != nil { + _u.SetActive(*v) + } + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *LocalityUpdateOne) SetUpdatedAt(v time.Time) *LocalityUpdateOne { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// AddProviderProfileIDs adds the "provider_profiles" edge to the ProviderProfile entity by IDs. +func (_u *LocalityUpdateOne) AddProviderProfileIDs(ids ...uuid.UUID) *LocalityUpdateOne { + _u.mutation.AddProviderProfileIDs(ids...) + return _u +} + +// AddProviderProfiles adds the "provider_profiles" edges to the ProviderProfile entity. +func (_u *LocalityUpdateOne) AddProviderProfiles(v ...*ProviderProfile) *LocalityUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddProviderProfileIDs(ids...) +} + +// Mutation returns the LocalityMutation object of the builder. +func (_u *LocalityUpdateOne) Mutation() *LocalityMutation { + return _u.mutation +} + +// ClearProviderProfiles clears all "provider_profiles" edges to the ProviderProfile entity. +func (_u *LocalityUpdateOne) ClearProviderProfiles() *LocalityUpdateOne { + _u.mutation.ClearProviderProfiles() + return _u +} + +// RemoveProviderProfileIDs removes the "provider_profiles" edge to ProviderProfile entities by IDs. +func (_u *LocalityUpdateOne) RemoveProviderProfileIDs(ids ...uuid.UUID) *LocalityUpdateOne { + _u.mutation.RemoveProviderProfileIDs(ids...) + return _u +} + +// RemoveProviderProfiles removes "provider_profiles" edges to ProviderProfile entities. +func (_u *LocalityUpdateOne) RemoveProviderProfiles(v ...*ProviderProfile) *LocalityUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveProviderProfileIDs(ids...) +} + +// Where appends a list predicates to the LocalityUpdate builder. +func (_u *LocalityUpdateOne) Where(ps ...predicate.Locality) *LocalityUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *LocalityUpdateOne) Select(field string, fields ...string) *LocalityUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated Locality entity. +func (_u *LocalityUpdateOne) Save(ctx context.Context) (*Locality, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *LocalityUpdateOne) SaveX(ctx context.Context) *Locality { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *LocalityUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *LocalityUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *LocalityUpdateOne) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := locality.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *LocalityUpdateOne) check() error { + if v, ok := _u.mutation.Name(); ok { + if err := locality.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Locality.name": %w`, err)} + } + } + if _u.mutation.ParentParishCleared() && len(_u.mutation.ParentParishIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "Locality.parent_parish"`) + } + return nil +} + +func (_u *LocalityUpdateOne) sqlSave(ctx context.Context) (_node *Locality, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(locality.Table, locality.Columns, sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Locality.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, locality.FieldID) + for _, f := range fields { + if !locality.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != locality.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(locality.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Active(); ok { + _spec.SetField(locality.FieldActive, field.TypeBool, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(locality.FieldUpdatedAt, field.TypeTime, value) + } + if _u.mutation.ProviderProfilesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: locality.ProviderProfilesTable, + Columns: locality.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedProviderProfilesIDs(); len(nodes) > 0 && !_u.mutation.ProviderProfilesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: locality.ProviderProfilesTable, + Columns: locality.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.ProviderProfilesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: locality.ProviderProfilesTable, + Columns: locality.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &Locality{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{locality.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/ent/migrate/schema.go b/backend/ent/migrate/schema.go index 6c8d2c5..f55af25 100644 --- a/backend/ent/migrate/schema.go +++ b/backend/ent/migrate/schema.go @@ -9,6 +9,45 @@ import ( ) var ( + // AdministrativeAreasColumns holds the columns for the "administrative_areas" table. + AdministrativeAreasColumns = []*schema.Column{ + {Name: "id", Type: field.TypeUUID}, + {Name: "source", Type: field.TypeString, Size: 40}, + {Name: "source_version", Type: field.TypeString, Size: 20}, + {Name: "external_code", Type: field.TypeString, Size: 32}, + {Name: "kind", Type: field.TypeString, Size: 20}, + {Name: "name", Type: field.TypeString, Size: 160}, + {Name: "active", Type: field.TypeBool, Default: true}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + {Name: "parent_id", Type: field.TypeUUID, Nullable: true}, + } + // AdministrativeAreasTable holds the schema information for the "administrative_areas" table. + AdministrativeAreasTable = &schema.Table{ + Name: "administrative_areas", + Columns: AdministrativeAreasColumns, + PrimaryKey: []*schema.Column{AdministrativeAreasColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "administrative_areas_administrative_areas_children", + Columns: []*schema.Column{AdministrativeAreasColumns[9]}, + RefColumns: []*schema.Column{AdministrativeAreasColumns[0]}, + OnDelete: schema.SetNull, + }, + }, + Indexes: []*schema.Index{ + { + Name: "administrativearea_source_external_code", + Unique: true, + Columns: []*schema.Column{AdministrativeAreasColumns[1], AdministrativeAreasColumns[3]}, + }, + { + Name: "administrativearea_parent_id_kind", + Unique: false, + Columns: []*schema.Column{AdministrativeAreasColumns[9], AdministrativeAreasColumns[4]}, + }, + }, + } // InternalUsersColumns holds the columns for the "internal_users" table. InternalUsersColumns = []*schema.Column{ {Name: "id", Type: field.TypeUUID}, @@ -22,6 +61,214 @@ var ( Columns: InternalUsersColumns, PrimaryKey: []*schema.Column{InternalUsersColumns[0]}, } + // LocalitiesColumns holds the columns for the "localities" table. + LocalitiesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeUUID}, + {Name: "slug", Type: field.TypeString, Unique: true, Size: 100}, + {Name: "name", Type: field.TypeString, Size: 160}, + {Name: "source", Type: field.TypeString, Size: 40}, + {Name: "source_element_id", Type: field.TypeString, Unique: true, Size: 32}, + {Name: "source_version", Type: field.TypeString, Size: 20}, + {Name: "source_retrieved_at", Type: field.TypeTime}, + {Name: "latitude", Type: field.TypeFloat64}, + {Name: "longitude", Type: field.TypeFloat64}, + {Name: "active", Type: field.TypeBool, Default: true}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + {Name: "parent_parish_id", Type: field.TypeUUID}, + } + // LocalitiesTable holds the schema information for the "localities" table. + LocalitiesTable = &schema.Table{ + Name: "localities", + Columns: LocalitiesColumns, + PrimaryKey: []*schema.Column{LocalitiesColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "localities_administrative_areas_localities", + Columns: []*schema.Column{LocalitiesColumns[12]}, + RefColumns: []*schema.Column{AdministrativeAreasColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + } + // ProviderProfilesColumns holds the columns for the "provider_profiles" table. + ProviderProfilesColumns = []*schema.Column{ + {Name: "internal_user_id", Type: field.TypeUUID}, + {Name: "display_name", Type: field.TypeString, Size: 100}, + {Name: "provider_type", Type: field.TypeString, Size: 20}, + {Name: "bio", Type: field.TypeString, Size: 1000}, + {Name: "primary_locality_id", Type: field.TypeUUID}, + {Name: "max_travel_distance_km", Type: field.TypeInt}, + {Name: "travels_to_customer", Type: field.TypeBool, Default: false}, + {Name: "receives_customer", Type: field.TypeBool, Default: false}, + {Name: "remote_services", Type: field.TypeBool, Default: false}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + } + // ProviderProfilesTable holds the schema information for the "provider_profiles" table. + ProviderProfilesTable = &schema.Table{ + Name: "provider_profiles", + Columns: ProviderProfilesColumns, + PrimaryKey: []*schema.Column{ProviderProfilesColumns[0]}, + } + // ProviderServiceLocalitiesColumns holds the columns for the "provider_service_localities" table. + ProviderServiceLocalitiesColumns = []*schema.Column{ + {Name: "internal_user_id", Type: field.TypeUUID}, + {Name: "locality_id", Type: field.TypeUUID}, + } + // ProviderServiceLocalitiesTable holds the schema information for the "provider_service_localities" table. + ProviderServiceLocalitiesTable = &schema.Table{ + Name: "provider_service_localities", + Columns: ProviderServiceLocalitiesColumns, + PrimaryKey: []*schema.Column{ProviderServiceLocalitiesColumns[0], ProviderServiceLocalitiesColumns[1]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "provider_service_localities_provider_profiles_profile", + Columns: []*schema.Column{ProviderServiceLocalitiesColumns[0]}, + RefColumns: []*schema.Column{ProviderProfilesColumns[0]}, + OnDelete: schema.NoAction, + }, + { + Symbol: "provider_service_localities_localities_locality", + Columns: []*schema.Column{ProviderServiceLocalitiesColumns[1]}, + RefColumns: []*schema.Column{LocalitiesColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + } + // ProviderSpokenLanguagesColumns holds the columns for the "provider_spoken_languages" table. + ProviderSpokenLanguagesColumns = []*schema.Column{ + {Name: "internal_user_id", Type: field.TypeUUID}, + {Name: "language_code", Type: field.TypeString, Size: 10}, + } + // ProviderSpokenLanguagesTable holds the schema information for the "provider_spoken_languages" table. + ProviderSpokenLanguagesTable = &schema.Table{ + Name: "provider_spoken_languages", + Columns: ProviderSpokenLanguagesColumns, + PrimaryKey: []*schema.Column{ProviderSpokenLanguagesColumns[0], ProviderSpokenLanguagesColumns[1]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "provider_spoken_languages_provider_profiles_profile", + Columns: []*schema.Column{ProviderSpokenLanguagesColumns[0]}, + RefColumns: []*schema.Column{ProviderProfilesColumns[0]}, + OnDelete: schema.NoAction, + }, + { + Symbol: "provider_spoken_languages_spoken_languages_language", + Columns: []*schema.Column{ProviderSpokenLanguagesColumns[1]}, + RefColumns: []*schema.Column{SpokenLanguagesColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + } + // ServiceCategoriesColumns holds the columns for the "service_categories" table. + ServiceCategoriesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeUUID}, + {Name: "slug", Type: field.TypeString, Unique: true, Size: 80}, + {Name: "active", Type: field.TypeBool, Default: true}, + {Name: "sort_order", Type: field.TypeInt}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + {Name: "parent_id", Type: field.TypeUUID, Nullable: true}, + } + // ServiceCategoriesTable holds the schema information for the "service_categories" table. + ServiceCategoriesTable = &schema.Table{ + Name: "service_categories", + Columns: ServiceCategoriesColumns, + PrimaryKey: []*schema.Column{ServiceCategoriesColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "service_categories_service_categories_children", + Columns: []*schema.Column{ServiceCategoriesColumns[6]}, + RefColumns: []*schema.Column{ServiceCategoriesColumns[0]}, + OnDelete: schema.SetNull, + }, + }, + Indexes: []*schema.Index{ + { + Name: "servicecategory_parent_id_sort_order", + Unique: false, + Columns: []*schema.Column{ServiceCategoriesColumns[6], ServiceCategoriesColumns[3]}, + }, + }, + } + // ServiceCategoryTranslationsColumns holds the columns for the "service_category_translations" table. + ServiceCategoryTranslationsColumns = []*schema.Column{ + {Name: "name", Type: field.TypeString, Size: 120}, + {Name: "description", Type: field.TypeString, Nullable: true, Size: 500}, + {Name: "category_id", Type: field.TypeUUID}, + {Name: "locale", Type: field.TypeString, Size: 10}, + } + // ServiceCategoryTranslationsTable holds the schema information for the "service_category_translations" table. + ServiceCategoryTranslationsTable = &schema.Table{ + Name: "service_category_translations", + Columns: ServiceCategoryTranslationsColumns, + PrimaryKey: []*schema.Column{ServiceCategoryTranslationsColumns[2], ServiceCategoryTranslationsColumns[3]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "service_category_translations_service_categories_category", + Columns: []*schema.Column{ServiceCategoryTranslationsColumns[2]}, + RefColumns: []*schema.Column{ServiceCategoriesColumns[0]}, + OnDelete: schema.NoAction, + }, + { + Symbol: "service_category_translations_supported_locales_locale_record", + Columns: []*schema.Column{ServiceCategoryTranslationsColumns[3]}, + RefColumns: []*schema.Column{SupportedLocalesColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + } + // SpokenLanguagesColumns holds the columns for the "spoken_languages" table. + SpokenLanguagesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Size: 10}, + {Name: "active", Type: field.TypeBool, Default: true}, + {Name: "sort_order", Type: field.TypeInt}, + } + // SpokenLanguagesTable holds the schema information for the "spoken_languages" table. + SpokenLanguagesTable = &schema.Table{ + Name: "spoken_languages", + Columns: SpokenLanguagesColumns, + PrimaryKey: []*schema.Column{SpokenLanguagesColumns[0]}, + } + // SpokenLanguageTranslationsColumns holds the columns for the "spoken_language_translations" table. + SpokenLanguageTranslationsColumns = []*schema.Column{ + {Name: "name", Type: field.TypeString, Size: 80}, + {Name: "language_code", Type: field.TypeString, Size: 10}, + {Name: "locale", Type: field.TypeString, Size: 10}, + } + // SpokenLanguageTranslationsTable holds the schema information for the "spoken_language_translations" table. + SpokenLanguageTranslationsTable = &schema.Table{ + Name: "spoken_language_translations", + Columns: SpokenLanguageTranslationsColumns, + PrimaryKey: []*schema.Column{SpokenLanguageTranslationsColumns[1], SpokenLanguageTranslationsColumns[2]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "spoken_language_translations_spoken_languages_language", + Columns: []*schema.Column{SpokenLanguageTranslationsColumns[1]}, + RefColumns: []*schema.Column{SpokenLanguagesColumns[0]}, + OnDelete: schema.NoAction, + }, + { + Symbol: "spoken_language_translations_supported_locales_locale_record", + Columns: []*schema.Column{SpokenLanguageTranslationsColumns[2]}, + RefColumns: []*schema.Column{SupportedLocalesColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + } + // SupportedLocalesColumns holds the columns for the "supported_locales" table. + SupportedLocalesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Size: 10}, + {Name: "active", Type: field.TypeBool, Default: true}, + {Name: "sort_order", Type: field.TypeInt}, + } + // SupportedLocalesTable holds the schema information for the "supported_locales" table. + SupportedLocalesTable = &schema.Table{ + Name: "supported_locales", + Columns: SupportedLocalesColumns, + PrimaryKey: []*schema.Column{SupportedLocalesColumns[0]}, + } // UserAccountsColumns holds the columns for the "user_accounts" table. UserAccountsColumns = []*schema.Column{ {Name: "internal_user_id", Type: field.TypeUUID}, @@ -38,15 +285,66 @@ var ( } // Tables holds all the tables in the schema. Tables = []*schema.Table{ + AdministrativeAreasTable, InternalUsersTable, + LocalitiesTable, + ProviderProfilesTable, + ProviderServiceLocalitiesTable, + ProviderSpokenLanguagesTable, + ServiceCategoriesTable, + ServiceCategoryTranslationsTable, + SpokenLanguagesTable, + SpokenLanguageTranslationsTable, + SupportedLocalesTable, UserAccountsTable, } ) func init() { + AdministrativeAreasTable.ForeignKeys[0].RefTable = AdministrativeAreasTable + AdministrativeAreasTable.Annotation = &entsql.Annotation{ + Table: "administrative_areas", + } InternalUsersTable.Annotation = &entsql.Annotation{ Table: "internal_users", } + LocalitiesTable.ForeignKeys[0].RefTable = AdministrativeAreasTable + LocalitiesTable.Annotation = &entsql.Annotation{ + Table: "localities", + } + ProviderProfilesTable.Annotation = &entsql.Annotation{ + Table: "provider_profiles", + } + ProviderServiceLocalitiesTable.ForeignKeys[0].RefTable = ProviderProfilesTable + ProviderServiceLocalitiesTable.ForeignKeys[1].RefTable = LocalitiesTable + ProviderServiceLocalitiesTable.Annotation = &entsql.Annotation{ + Table: "provider_service_localities", + } + ProviderSpokenLanguagesTable.ForeignKeys[0].RefTable = ProviderProfilesTable + ProviderSpokenLanguagesTable.ForeignKeys[1].RefTable = SpokenLanguagesTable + ProviderSpokenLanguagesTable.Annotation = &entsql.Annotation{ + Table: "provider_spoken_languages", + } + ServiceCategoriesTable.ForeignKeys[0].RefTable = ServiceCategoriesTable + ServiceCategoriesTable.Annotation = &entsql.Annotation{ + Table: "service_categories", + } + ServiceCategoryTranslationsTable.ForeignKeys[0].RefTable = ServiceCategoriesTable + ServiceCategoryTranslationsTable.ForeignKeys[1].RefTable = SupportedLocalesTable + ServiceCategoryTranslationsTable.Annotation = &entsql.Annotation{ + Table: "service_category_translations", + } + SpokenLanguagesTable.Annotation = &entsql.Annotation{ + Table: "spoken_languages", + } + SpokenLanguageTranslationsTable.ForeignKeys[0].RefTable = SpokenLanguagesTable + SpokenLanguageTranslationsTable.ForeignKeys[1].RefTable = SupportedLocalesTable + SpokenLanguageTranslationsTable.Annotation = &entsql.Annotation{ + Table: "spoken_language_translations", + } + SupportedLocalesTable.Annotation = &entsql.Annotation{ + Table: "supported_locales", + } UserAccountsTable.Annotation = &entsql.Annotation{ Table: "user_accounts", } diff --git a/backend/ent/mutation.go b/backend/ent/mutation.go index 5a5bace..ff85557 100644 --- a/backend/ent/mutation.go +++ b/backend/ent/mutation.go @@ -11,8 +11,18 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/administrativearea" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerservicelocality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerspokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategorytranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguagetranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" "github.com/google/uuid" ) @@ -26,36 +36,6865 @@ const ( OpUpdateOne = ent.OpUpdateOne // Node types. - TypeInternalUser = "InternalUser" - TypeUserAccount = "UserAccount" + TypeAdministrativeArea = "AdministrativeArea" + TypeInternalUser = "InternalUser" + TypeLocality = "Locality" + TypeProviderProfile = "ProviderProfile" + TypeProviderServiceLocality = "ProviderServiceLocality" + TypeProviderSpokenLanguage = "ProviderSpokenLanguage" + TypeServiceCategory = "ServiceCategory" + TypeServiceCategoryTranslation = "ServiceCategoryTranslation" + TypeSpokenLanguage = "SpokenLanguage" + TypeSpokenLanguageTranslation = "SpokenLanguageTranslation" + TypeSupportedLocale = "SupportedLocale" + TypeUserAccount = "UserAccount" ) +// AdministrativeAreaMutation represents an operation that mutates the AdministrativeArea nodes in the graph. +type AdministrativeAreaMutation struct { + config + op Op + typ string + id *uuid.UUID + source *string + source_version *string + external_code *string + kind *string + name *string + active *bool + created_at *time.Time + updated_at *time.Time + clearedFields map[string]struct{} + children map[uuid.UUID]struct{} + removedchildren map[uuid.UUID]struct{} + clearedchildren bool + parent *uuid.UUID + clearedparent bool + localities map[uuid.UUID]struct{} + removedlocalities map[uuid.UUID]struct{} + clearedlocalities bool + done bool + oldValue func(context.Context) (*AdministrativeArea, error) + predicates []predicate.AdministrativeArea +} + +var _ ent.Mutation = (*AdministrativeAreaMutation)(nil) + +// administrativeareaOption allows management of the mutation configuration using functional options. +type administrativeareaOption func(*AdministrativeAreaMutation) + +// newAdministrativeAreaMutation creates new mutation for the AdministrativeArea entity. +func newAdministrativeAreaMutation(c config, op Op, opts ...administrativeareaOption) *AdministrativeAreaMutation { + m := &AdministrativeAreaMutation{ + config: c, + op: op, + typ: TypeAdministrativeArea, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withAdministrativeAreaID sets the ID field of the mutation. +func withAdministrativeAreaID(id uuid.UUID) administrativeareaOption { + return func(m *AdministrativeAreaMutation) { + var ( + err error + once sync.Once + value *AdministrativeArea + ) + m.oldValue = func(ctx context.Context) (*AdministrativeArea, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().AdministrativeArea.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withAdministrativeArea sets the old AdministrativeArea of the mutation. +func withAdministrativeArea(node *AdministrativeArea) administrativeareaOption { + return func(m *AdministrativeAreaMutation) { + m.oldValue = func(context.Context) (*AdministrativeArea, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m AdministrativeAreaMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m AdministrativeAreaMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of AdministrativeArea entities. +func (m *AdministrativeAreaMutation) SetID(id uuid.UUID) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *AdministrativeAreaMutation) ID() (id uuid.UUID, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *AdministrativeAreaMutation) IDs(ctx context.Context) ([]uuid.UUID, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []uuid.UUID{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().AdministrativeArea.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetSource sets the "source" field. +func (m *AdministrativeAreaMutation) SetSource(s string) { + m.source = &s +} + +// Source returns the value of the "source" field in the mutation. +func (m *AdministrativeAreaMutation) Source() (r string, exists bool) { + v := m.source + if v == nil { + return + } + return *v, true +} + +// OldSource returns the old "source" field's value of the AdministrativeArea entity. +// If the AdministrativeArea object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AdministrativeAreaMutation) OldSource(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSource is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSource requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSource: %w", err) + } + return oldValue.Source, nil +} + +// ResetSource resets all changes to the "source" field. +func (m *AdministrativeAreaMutation) ResetSource() { + m.source = nil +} + +// SetSourceVersion sets the "source_version" field. +func (m *AdministrativeAreaMutation) SetSourceVersion(s string) { + m.source_version = &s +} + +// SourceVersion returns the value of the "source_version" field in the mutation. +func (m *AdministrativeAreaMutation) SourceVersion() (r string, exists bool) { + v := m.source_version + if v == nil { + return + } + return *v, true +} + +// OldSourceVersion returns the old "source_version" field's value of the AdministrativeArea entity. +// If the AdministrativeArea object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AdministrativeAreaMutation) OldSourceVersion(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSourceVersion is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSourceVersion requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSourceVersion: %w", err) + } + return oldValue.SourceVersion, nil +} + +// ResetSourceVersion resets all changes to the "source_version" field. +func (m *AdministrativeAreaMutation) ResetSourceVersion() { + m.source_version = nil +} + +// SetExternalCode sets the "external_code" field. +func (m *AdministrativeAreaMutation) SetExternalCode(s string) { + m.external_code = &s +} + +// ExternalCode returns the value of the "external_code" field in the mutation. +func (m *AdministrativeAreaMutation) ExternalCode() (r string, exists bool) { + v := m.external_code + if v == nil { + return + } + return *v, true +} + +// OldExternalCode returns the old "external_code" field's value of the AdministrativeArea entity. +// If the AdministrativeArea object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AdministrativeAreaMutation) OldExternalCode(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldExternalCode is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldExternalCode requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldExternalCode: %w", err) + } + return oldValue.ExternalCode, nil +} + +// ResetExternalCode resets all changes to the "external_code" field. +func (m *AdministrativeAreaMutation) ResetExternalCode() { + m.external_code = nil +} + +// SetKind sets the "kind" field. +func (m *AdministrativeAreaMutation) SetKind(s string) { + m.kind = &s +} + +// Kind returns the value of the "kind" field in the mutation. +func (m *AdministrativeAreaMutation) Kind() (r string, exists bool) { + v := m.kind + if v == nil { + return + } + return *v, true +} + +// OldKind returns the old "kind" field's value of the AdministrativeArea entity. +// If the AdministrativeArea object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AdministrativeAreaMutation) OldKind(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldKind is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldKind requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldKind: %w", err) + } + return oldValue.Kind, nil +} + +// ResetKind resets all changes to the "kind" field. +func (m *AdministrativeAreaMutation) ResetKind() { + m.kind = nil +} + +// SetName sets the "name" field. +func (m *AdministrativeAreaMutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *AdministrativeAreaMutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return + } + return *v, true +} + +// OldName returns the old "name" field's value of the AdministrativeArea entity. +// If the AdministrativeArea object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AdministrativeAreaMutation) OldName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldName: %w", err) + } + return oldValue.Name, nil +} + +// ResetName resets all changes to the "name" field. +func (m *AdministrativeAreaMutation) ResetName() { + m.name = nil +} + +// SetParentID sets the "parent_id" field. +func (m *AdministrativeAreaMutation) SetParentID(u uuid.UUID) { + m.parent = &u +} + +// ParentID returns the value of the "parent_id" field in the mutation. +func (m *AdministrativeAreaMutation) ParentID() (r uuid.UUID, exists bool) { + v := m.parent + if v == nil { + return + } + return *v, true +} + +// OldParentID returns the old "parent_id" field's value of the AdministrativeArea entity. +// If the AdministrativeArea object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AdministrativeAreaMutation) OldParentID(ctx context.Context) (v *uuid.UUID, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldParentID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldParentID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldParentID: %w", err) + } + return oldValue.ParentID, nil +} + +// ClearParentID clears the value of the "parent_id" field. +func (m *AdministrativeAreaMutation) ClearParentID() { + m.parent = nil + m.clearedFields[administrativearea.FieldParentID] = struct{}{} +} + +// ParentIDCleared returns if the "parent_id" field was cleared in this mutation. +func (m *AdministrativeAreaMutation) ParentIDCleared() bool { + _, ok := m.clearedFields[administrativearea.FieldParentID] + return ok +} + +// ResetParentID resets all changes to the "parent_id" field. +func (m *AdministrativeAreaMutation) ResetParentID() { + m.parent = nil + delete(m.clearedFields, administrativearea.FieldParentID) +} + +// SetActive sets the "active" field. +func (m *AdministrativeAreaMutation) SetActive(b bool) { + m.active = &b +} + +// Active returns the value of the "active" field in the mutation. +func (m *AdministrativeAreaMutation) Active() (r bool, exists bool) { + v := m.active + if v == nil { + return + } + return *v, true +} + +// OldActive returns the old "active" field's value of the AdministrativeArea entity. +// If the AdministrativeArea object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AdministrativeAreaMutation) OldActive(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldActive is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldActive requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldActive: %w", err) + } + return oldValue.Active, nil +} + +// ResetActive resets all changes to the "active" field. +func (m *AdministrativeAreaMutation) ResetActive() { + m.active = nil +} + +// SetCreatedAt sets the "created_at" field. +func (m *AdministrativeAreaMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *AdministrativeAreaMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the AdministrativeArea entity. +// If the AdministrativeArea object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AdministrativeAreaMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *AdministrativeAreaMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *AdministrativeAreaMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *AdministrativeAreaMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the AdministrativeArea entity. +// If the AdministrativeArea object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AdministrativeAreaMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *AdministrativeAreaMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// AddChildIDs adds the "children" edge to the AdministrativeArea entity by ids. +func (m *AdministrativeAreaMutation) AddChildIDs(ids ...uuid.UUID) { + if m.children == nil { + m.children = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.children[ids[i]] = struct{}{} + } +} + +// ClearChildren clears the "children" edge to the AdministrativeArea entity. +func (m *AdministrativeAreaMutation) ClearChildren() { + m.clearedchildren = true +} + +// ChildrenCleared reports if the "children" edge to the AdministrativeArea entity was cleared. +func (m *AdministrativeAreaMutation) ChildrenCleared() bool { + return m.clearedchildren +} + +// RemoveChildIDs removes the "children" edge to the AdministrativeArea entity by IDs. +func (m *AdministrativeAreaMutation) RemoveChildIDs(ids ...uuid.UUID) { + if m.removedchildren == nil { + m.removedchildren = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.children, ids[i]) + m.removedchildren[ids[i]] = struct{}{} + } +} + +// RemovedChildren returns the removed IDs of the "children" edge to the AdministrativeArea entity. +func (m *AdministrativeAreaMutation) RemovedChildrenIDs() (ids []uuid.UUID) { + for id := range m.removedchildren { + ids = append(ids, id) + } + return +} + +// ChildrenIDs returns the "children" edge IDs in the mutation. +func (m *AdministrativeAreaMutation) ChildrenIDs() (ids []uuid.UUID) { + for id := range m.children { + ids = append(ids, id) + } + return +} + +// ResetChildren resets all changes to the "children" edge. +func (m *AdministrativeAreaMutation) ResetChildren() { + m.children = nil + m.clearedchildren = false + m.removedchildren = nil +} + +// ClearParent clears the "parent" edge to the AdministrativeArea entity. +func (m *AdministrativeAreaMutation) ClearParent() { + m.clearedparent = true + m.clearedFields[administrativearea.FieldParentID] = struct{}{} +} + +// ParentCleared reports if the "parent" edge to the AdministrativeArea entity was cleared. +func (m *AdministrativeAreaMutation) ParentCleared() bool { + return m.ParentIDCleared() || m.clearedparent +} + +// ParentIDs returns the "parent" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// ParentID instead. It exists only for internal usage by the builders. +func (m *AdministrativeAreaMutation) ParentIDs() (ids []uuid.UUID) { + if id := m.parent; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetParent resets all changes to the "parent" edge. +func (m *AdministrativeAreaMutation) ResetParent() { + m.parent = nil + m.clearedparent = false +} + +// AddLocalityIDs adds the "localities" edge to the Locality entity by ids. +func (m *AdministrativeAreaMutation) AddLocalityIDs(ids ...uuid.UUID) { + if m.localities == nil { + m.localities = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.localities[ids[i]] = struct{}{} + } +} + +// ClearLocalities clears the "localities" edge to the Locality entity. +func (m *AdministrativeAreaMutation) ClearLocalities() { + m.clearedlocalities = true +} + +// LocalitiesCleared reports if the "localities" edge to the Locality entity was cleared. +func (m *AdministrativeAreaMutation) LocalitiesCleared() bool { + return m.clearedlocalities +} + +// RemoveLocalityIDs removes the "localities" edge to the Locality entity by IDs. +func (m *AdministrativeAreaMutation) RemoveLocalityIDs(ids ...uuid.UUID) { + if m.removedlocalities == nil { + m.removedlocalities = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.localities, ids[i]) + m.removedlocalities[ids[i]] = struct{}{} + } +} + +// RemovedLocalities returns the removed IDs of the "localities" edge to the Locality entity. +func (m *AdministrativeAreaMutation) RemovedLocalitiesIDs() (ids []uuid.UUID) { + for id := range m.removedlocalities { + ids = append(ids, id) + } + return +} + +// LocalitiesIDs returns the "localities" edge IDs in the mutation. +func (m *AdministrativeAreaMutation) LocalitiesIDs() (ids []uuid.UUID) { + for id := range m.localities { + ids = append(ids, id) + } + return +} + +// ResetLocalities resets all changes to the "localities" edge. +func (m *AdministrativeAreaMutation) ResetLocalities() { + m.localities = nil + m.clearedlocalities = false + m.removedlocalities = nil +} + +// Where appends a list predicates to the AdministrativeAreaMutation builder. +func (m *AdministrativeAreaMutation) Where(ps ...predicate.AdministrativeArea) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the AdministrativeAreaMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *AdministrativeAreaMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.AdministrativeArea, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *AdministrativeAreaMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *AdministrativeAreaMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (AdministrativeArea). +func (m *AdministrativeAreaMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *AdministrativeAreaMutation) Fields() []string { + fields := make([]string, 0, 9) + if m.source != nil { + fields = append(fields, administrativearea.FieldSource) + } + if m.source_version != nil { + fields = append(fields, administrativearea.FieldSourceVersion) + } + if m.external_code != nil { + fields = append(fields, administrativearea.FieldExternalCode) + } + if m.kind != nil { + fields = append(fields, administrativearea.FieldKind) + } + if m.name != nil { + fields = append(fields, administrativearea.FieldName) + } + if m.parent != nil { + fields = append(fields, administrativearea.FieldParentID) + } + if m.active != nil { + fields = append(fields, administrativearea.FieldActive) + } + if m.created_at != nil { + fields = append(fields, administrativearea.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, administrativearea.FieldUpdatedAt) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *AdministrativeAreaMutation) Field(name string) (ent.Value, bool) { + switch name { + case administrativearea.FieldSource: + return m.Source() + case administrativearea.FieldSourceVersion: + return m.SourceVersion() + case administrativearea.FieldExternalCode: + return m.ExternalCode() + case administrativearea.FieldKind: + return m.Kind() + case administrativearea.FieldName: + return m.Name() + case administrativearea.FieldParentID: + return m.ParentID() + case administrativearea.FieldActive: + return m.Active() + case administrativearea.FieldCreatedAt: + return m.CreatedAt() + case administrativearea.FieldUpdatedAt: + return m.UpdatedAt() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *AdministrativeAreaMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case administrativearea.FieldSource: + return m.OldSource(ctx) + case administrativearea.FieldSourceVersion: + return m.OldSourceVersion(ctx) + case administrativearea.FieldExternalCode: + return m.OldExternalCode(ctx) + case administrativearea.FieldKind: + return m.OldKind(ctx) + case administrativearea.FieldName: + return m.OldName(ctx) + case administrativearea.FieldParentID: + return m.OldParentID(ctx) + case administrativearea.FieldActive: + return m.OldActive(ctx) + case administrativearea.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case administrativearea.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + } + return nil, fmt.Errorf("unknown AdministrativeArea field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AdministrativeAreaMutation) SetField(name string, value ent.Value) error { + switch name { + case administrativearea.FieldSource: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSource(v) + return nil + case administrativearea.FieldSourceVersion: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSourceVersion(v) + return nil + case administrativearea.FieldExternalCode: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetExternalCode(v) + return nil + case administrativearea.FieldKind: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetKind(v) + return nil + case administrativearea.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + case administrativearea.FieldParentID: + v, ok := value.(uuid.UUID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetParentID(v) + return nil + case administrativearea.FieldActive: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetActive(v) + return nil + case administrativearea.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case administrativearea.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown AdministrativeArea field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *AdministrativeAreaMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *AdministrativeAreaMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AdministrativeAreaMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown AdministrativeArea numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *AdministrativeAreaMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(administrativearea.FieldParentID) { + fields = append(fields, administrativearea.FieldParentID) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *AdministrativeAreaMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *AdministrativeAreaMutation) ClearField(name string) error { + switch name { + case administrativearea.FieldParentID: + m.ClearParentID() + return nil + } + return fmt.Errorf("unknown AdministrativeArea nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *AdministrativeAreaMutation) ResetField(name string) error { + switch name { + case administrativearea.FieldSource: + m.ResetSource() + return nil + case administrativearea.FieldSourceVersion: + m.ResetSourceVersion() + return nil + case administrativearea.FieldExternalCode: + m.ResetExternalCode() + return nil + case administrativearea.FieldKind: + m.ResetKind() + return nil + case administrativearea.FieldName: + m.ResetName() + return nil + case administrativearea.FieldParentID: + m.ResetParentID() + return nil + case administrativearea.FieldActive: + m.ResetActive() + return nil + case administrativearea.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case administrativearea.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + } + return fmt.Errorf("unknown AdministrativeArea field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *AdministrativeAreaMutation) AddedEdges() []string { + edges := make([]string, 0, 3) + if m.children != nil { + edges = append(edges, administrativearea.EdgeChildren) + } + if m.parent != nil { + edges = append(edges, administrativearea.EdgeParent) + } + if m.localities != nil { + edges = append(edges, administrativearea.EdgeLocalities) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *AdministrativeAreaMutation) AddedIDs(name string) []ent.Value { + switch name { + case administrativearea.EdgeChildren: + ids := make([]ent.Value, 0, len(m.children)) + for id := range m.children { + ids = append(ids, id) + } + return ids + case administrativearea.EdgeParent: + if id := m.parent; id != nil { + return []ent.Value{*id} + } + case administrativearea.EdgeLocalities: + ids := make([]ent.Value, 0, len(m.localities)) + for id := range m.localities { + ids = append(ids, id) + } + return ids + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *AdministrativeAreaMutation) RemovedEdges() []string { + edges := make([]string, 0, 3) + if m.removedchildren != nil { + edges = append(edges, administrativearea.EdgeChildren) + } + if m.removedlocalities != nil { + edges = append(edges, administrativearea.EdgeLocalities) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *AdministrativeAreaMutation) RemovedIDs(name string) []ent.Value { + switch name { + case administrativearea.EdgeChildren: + ids := make([]ent.Value, 0, len(m.removedchildren)) + for id := range m.removedchildren { + ids = append(ids, id) + } + return ids + case administrativearea.EdgeLocalities: + ids := make([]ent.Value, 0, len(m.removedlocalities)) + for id := range m.removedlocalities { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *AdministrativeAreaMutation) ClearedEdges() []string { + edges := make([]string, 0, 3) + if m.clearedchildren { + edges = append(edges, administrativearea.EdgeChildren) + } + if m.clearedparent { + edges = append(edges, administrativearea.EdgeParent) + } + if m.clearedlocalities { + edges = append(edges, administrativearea.EdgeLocalities) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *AdministrativeAreaMutation) EdgeCleared(name string) bool { + switch name { + case administrativearea.EdgeChildren: + return m.clearedchildren + case administrativearea.EdgeParent: + return m.clearedparent + case administrativearea.EdgeLocalities: + return m.clearedlocalities + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *AdministrativeAreaMutation) ClearEdge(name string) error { + switch name { + case administrativearea.EdgeParent: + m.ClearParent() + return nil + } + return fmt.Errorf("unknown AdministrativeArea unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *AdministrativeAreaMutation) ResetEdge(name string) error { + switch name { + case administrativearea.EdgeChildren: + m.ResetChildren() + return nil + case administrativearea.EdgeParent: + m.ResetParent() + return nil + case administrativearea.EdgeLocalities: + m.ResetLocalities() + return nil + } + return fmt.Errorf("unknown AdministrativeArea edge %s", name) +} + // InternalUserMutation represents an operation that mutates the InternalUser nodes in the graph. type InternalUserMutation struct { config - op Op - typ string - id *uuid.UUID - clerk_subject *string - created_at *time.Time - updated_at *time.Time - clearedFields map[string]struct{} - done bool - oldValue func(context.Context) (*InternalUser, error) - predicates []predicate.InternalUser + op Op + typ string + id *uuid.UUID + clerk_subject *string + created_at *time.Time + updated_at *time.Time + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*InternalUser, error) + predicates []predicate.InternalUser +} + +var _ ent.Mutation = (*InternalUserMutation)(nil) + +// internaluserOption allows management of the mutation configuration using functional options. +type internaluserOption func(*InternalUserMutation) + +// newInternalUserMutation creates new mutation for the InternalUser entity. +func newInternalUserMutation(c config, op Op, opts ...internaluserOption) *InternalUserMutation { + m := &InternalUserMutation{ + config: c, + op: op, + typ: TypeInternalUser, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withInternalUserID sets the ID field of the mutation. +func withInternalUserID(id uuid.UUID) internaluserOption { + return func(m *InternalUserMutation) { + var ( + err error + once sync.Once + value *InternalUser + ) + m.oldValue = func(ctx context.Context) (*InternalUser, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().InternalUser.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withInternalUser sets the old InternalUser of the mutation. +func withInternalUser(node *InternalUser) internaluserOption { + return func(m *InternalUserMutation) { + m.oldValue = func(context.Context) (*InternalUser, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m InternalUserMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m InternalUserMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of InternalUser entities. +func (m *InternalUserMutation) SetID(id uuid.UUID) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *InternalUserMutation) ID() (id uuid.UUID, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *InternalUserMutation) IDs(ctx context.Context) ([]uuid.UUID, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []uuid.UUID{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().InternalUser.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetClerkSubject sets the "clerk_subject" field. +func (m *InternalUserMutation) SetClerkSubject(s string) { + m.clerk_subject = &s +} + +// ClerkSubject returns the value of the "clerk_subject" field in the mutation. +func (m *InternalUserMutation) ClerkSubject() (r string, exists bool) { + v := m.clerk_subject + if v == nil { + return + } + return *v, true +} + +// OldClerkSubject returns the old "clerk_subject" field's value of the InternalUser entity. +// If the InternalUser object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *InternalUserMutation) OldClerkSubject(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldClerkSubject is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldClerkSubject requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldClerkSubject: %w", err) + } + return oldValue.ClerkSubject, nil +} + +// ResetClerkSubject resets all changes to the "clerk_subject" field. +func (m *InternalUserMutation) ResetClerkSubject() { + m.clerk_subject = nil +} + +// SetCreatedAt sets the "created_at" field. +func (m *InternalUserMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *InternalUserMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the InternalUser entity. +// If the InternalUser object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *InternalUserMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *InternalUserMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *InternalUserMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *InternalUserMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the InternalUser entity. +// If the InternalUser object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *InternalUserMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *InternalUserMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// Where appends a list predicates to the InternalUserMutation builder. +func (m *InternalUserMutation) Where(ps ...predicate.InternalUser) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the InternalUserMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *InternalUserMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.InternalUser, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *InternalUserMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *InternalUserMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (InternalUser). +func (m *InternalUserMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *InternalUserMutation) Fields() []string { + fields := make([]string, 0, 3) + if m.clerk_subject != nil { + fields = append(fields, internaluser.FieldClerkSubject) + } + if m.created_at != nil { + fields = append(fields, internaluser.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, internaluser.FieldUpdatedAt) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *InternalUserMutation) Field(name string) (ent.Value, bool) { + switch name { + case internaluser.FieldClerkSubject: + return m.ClerkSubject() + case internaluser.FieldCreatedAt: + return m.CreatedAt() + case internaluser.FieldUpdatedAt: + return m.UpdatedAt() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *InternalUserMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case internaluser.FieldClerkSubject: + return m.OldClerkSubject(ctx) + case internaluser.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case internaluser.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + } + return nil, fmt.Errorf("unknown InternalUser field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *InternalUserMutation) SetField(name string, value ent.Value) error { + switch name { + case internaluser.FieldClerkSubject: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetClerkSubject(v) + return nil + case internaluser.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case internaluser.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown InternalUser field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *InternalUserMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *InternalUserMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *InternalUserMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown InternalUser numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *InternalUserMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *InternalUserMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *InternalUserMutation) ClearField(name string) error { + return fmt.Errorf("unknown InternalUser nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *InternalUserMutation) ResetField(name string) error { + switch name { + case internaluser.FieldClerkSubject: + m.ResetClerkSubject() + return nil + case internaluser.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case internaluser.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + } + return fmt.Errorf("unknown InternalUser field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *InternalUserMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *InternalUserMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *InternalUserMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *InternalUserMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *InternalUserMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *InternalUserMutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *InternalUserMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown InternalUser unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *InternalUserMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown InternalUser edge %s", name) +} + +// LocalityMutation represents an operation that mutates the Locality nodes in the graph. +type LocalityMutation struct { + config + op Op + typ string + id *uuid.UUID + slug *string + name *string + source *string + source_element_id *string + source_version *string + source_retrieved_at *time.Time + latitude *float64 + addlatitude *float64 + longitude *float64 + addlongitude *float64 + active *bool + created_at *time.Time + updated_at *time.Time + clearedFields map[string]struct{} + parent_parish *uuid.UUID + clearedparent_parish bool + provider_profiles map[uuid.UUID]struct{} + removedprovider_profiles map[uuid.UUID]struct{} + clearedprovider_profiles bool + done bool + oldValue func(context.Context) (*Locality, error) + predicates []predicate.Locality +} + +var _ ent.Mutation = (*LocalityMutation)(nil) + +// localityOption allows management of the mutation configuration using functional options. +type localityOption func(*LocalityMutation) + +// newLocalityMutation creates new mutation for the Locality entity. +func newLocalityMutation(c config, op Op, opts ...localityOption) *LocalityMutation { + m := &LocalityMutation{ + config: c, + op: op, + typ: TypeLocality, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withLocalityID sets the ID field of the mutation. +func withLocalityID(id uuid.UUID) localityOption { + return func(m *LocalityMutation) { + var ( + err error + once sync.Once + value *Locality + ) + m.oldValue = func(ctx context.Context) (*Locality, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Locality.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withLocality sets the old Locality of the mutation. +func withLocality(node *Locality) localityOption { + return func(m *LocalityMutation) { + m.oldValue = func(context.Context) (*Locality, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m LocalityMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m LocalityMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of Locality entities. +func (m *LocalityMutation) SetID(id uuid.UUID) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *LocalityMutation) ID() (id uuid.UUID, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *LocalityMutation) IDs(ctx context.Context) ([]uuid.UUID, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []uuid.UUID{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().Locality.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetSlug sets the "slug" field. +func (m *LocalityMutation) SetSlug(s string) { + m.slug = &s +} + +// Slug returns the value of the "slug" field in the mutation. +func (m *LocalityMutation) Slug() (r string, exists bool) { + v := m.slug + if v == nil { + return + } + return *v, true +} + +// OldSlug returns the old "slug" field's value of the Locality entity. +// If the Locality object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *LocalityMutation) OldSlug(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSlug is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSlug requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSlug: %w", err) + } + return oldValue.Slug, nil +} + +// ResetSlug resets all changes to the "slug" field. +func (m *LocalityMutation) ResetSlug() { + m.slug = nil +} + +// SetName sets the "name" field. +func (m *LocalityMutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *LocalityMutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return + } + return *v, true +} + +// OldName returns the old "name" field's value of the Locality entity. +// If the Locality object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *LocalityMutation) OldName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldName: %w", err) + } + return oldValue.Name, nil +} + +// ResetName resets all changes to the "name" field. +func (m *LocalityMutation) ResetName() { + m.name = nil +} + +// SetParentParishID sets the "parent_parish_id" field. +func (m *LocalityMutation) SetParentParishID(u uuid.UUID) { + m.parent_parish = &u +} + +// ParentParishID returns the value of the "parent_parish_id" field in the mutation. +func (m *LocalityMutation) ParentParishID() (r uuid.UUID, exists bool) { + v := m.parent_parish + if v == nil { + return + } + return *v, true +} + +// OldParentParishID returns the old "parent_parish_id" field's value of the Locality entity. +// If the Locality object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *LocalityMutation) OldParentParishID(ctx context.Context) (v uuid.UUID, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldParentParishID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldParentParishID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldParentParishID: %w", err) + } + return oldValue.ParentParishID, nil +} + +// ResetParentParishID resets all changes to the "parent_parish_id" field. +func (m *LocalityMutation) ResetParentParishID() { + m.parent_parish = nil +} + +// SetSource sets the "source" field. +func (m *LocalityMutation) SetSource(s string) { + m.source = &s +} + +// Source returns the value of the "source" field in the mutation. +func (m *LocalityMutation) Source() (r string, exists bool) { + v := m.source + if v == nil { + return + } + return *v, true +} + +// OldSource returns the old "source" field's value of the Locality entity. +// If the Locality object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *LocalityMutation) OldSource(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSource is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSource requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSource: %w", err) + } + return oldValue.Source, nil +} + +// ResetSource resets all changes to the "source" field. +func (m *LocalityMutation) ResetSource() { + m.source = nil +} + +// SetSourceElementID sets the "source_element_id" field. +func (m *LocalityMutation) SetSourceElementID(s string) { + m.source_element_id = &s +} + +// SourceElementID returns the value of the "source_element_id" field in the mutation. +func (m *LocalityMutation) SourceElementID() (r string, exists bool) { + v := m.source_element_id + if v == nil { + return + } + return *v, true +} + +// OldSourceElementID returns the old "source_element_id" field's value of the Locality entity. +// If the Locality object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *LocalityMutation) OldSourceElementID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSourceElementID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSourceElementID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSourceElementID: %w", err) + } + return oldValue.SourceElementID, nil +} + +// ResetSourceElementID resets all changes to the "source_element_id" field. +func (m *LocalityMutation) ResetSourceElementID() { + m.source_element_id = nil +} + +// SetSourceVersion sets the "source_version" field. +func (m *LocalityMutation) SetSourceVersion(s string) { + m.source_version = &s +} + +// SourceVersion returns the value of the "source_version" field in the mutation. +func (m *LocalityMutation) SourceVersion() (r string, exists bool) { + v := m.source_version + if v == nil { + return + } + return *v, true +} + +// OldSourceVersion returns the old "source_version" field's value of the Locality entity. +// If the Locality object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *LocalityMutation) OldSourceVersion(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSourceVersion is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSourceVersion requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSourceVersion: %w", err) + } + return oldValue.SourceVersion, nil +} + +// ResetSourceVersion resets all changes to the "source_version" field. +func (m *LocalityMutation) ResetSourceVersion() { + m.source_version = nil +} + +// SetSourceRetrievedAt sets the "source_retrieved_at" field. +func (m *LocalityMutation) SetSourceRetrievedAt(t time.Time) { + m.source_retrieved_at = &t +} + +// SourceRetrievedAt returns the value of the "source_retrieved_at" field in the mutation. +func (m *LocalityMutation) SourceRetrievedAt() (r time.Time, exists bool) { + v := m.source_retrieved_at + if v == nil { + return + } + return *v, true +} + +// OldSourceRetrievedAt returns the old "source_retrieved_at" field's value of the Locality entity. +// If the Locality object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *LocalityMutation) OldSourceRetrievedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSourceRetrievedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSourceRetrievedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSourceRetrievedAt: %w", err) + } + return oldValue.SourceRetrievedAt, nil +} + +// ResetSourceRetrievedAt resets all changes to the "source_retrieved_at" field. +func (m *LocalityMutation) ResetSourceRetrievedAt() { + m.source_retrieved_at = nil +} + +// SetLatitude sets the "latitude" field. +func (m *LocalityMutation) SetLatitude(f float64) { + m.latitude = &f + m.addlatitude = nil +} + +// Latitude returns the value of the "latitude" field in the mutation. +func (m *LocalityMutation) Latitude() (r float64, exists bool) { + v := m.latitude + if v == nil { + return + } + return *v, true +} + +// OldLatitude returns the old "latitude" field's value of the Locality entity. +// If the Locality object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *LocalityMutation) OldLatitude(ctx context.Context) (v float64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLatitude is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLatitude requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLatitude: %w", err) + } + return oldValue.Latitude, nil +} + +// AddLatitude adds f to the "latitude" field. +func (m *LocalityMutation) AddLatitude(f float64) { + if m.addlatitude != nil { + *m.addlatitude += f + } else { + m.addlatitude = &f + } +} + +// AddedLatitude returns the value that was added to the "latitude" field in this mutation. +func (m *LocalityMutation) AddedLatitude() (r float64, exists bool) { + v := m.addlatitude + if v == nil { + return + } + return *v, true +} + +// ResetLatitude resets all changes to the "latitude" field. +func (m *LocalityMutation) ResetLatitude() { + m.latitude = nil + m.addlatitude = nil +} + +// SetLongitude sets the "longitude" field. +func (m *LocalityMutation) SetLongitude(f float64) { + m.longitude = &f + m.addlongitude = nil +} + +// Longitude returns the value of the "longitude" field in the mutation. +func (m *LocalityMutation) Longitude() (r float64, exists bool) { + v := m.longitude + if v == nil { + return + } + return *v, true +} + +// OldLongitude returns the old "longitude" field's value of the Locality entity. +// If the Locality object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *LocalityMutation) OldLongitude(ctx context.Context) (v float64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLongitude is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLongitude requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLongitude: %w", err) + } + return oldValue.Longitude, nil +} + +// AddLongitude adds f to the "longitude" field. +func (m *LocalityMutation) AddLongitude(f float64) { + if m.addlongitude != nil { + *m.addlongitude += f + } else { + m.addlongitude = &f + } +} + +// AddedLongitude returns the value that was added to the "longitude" field in this mutation. +func (m *LocalityMutation) AddedLongitude() (r float64, exists bool) { + v := m.addlongitude + if v == nil { + return + } + return *v, true +} + +// ResetLongitude resets all changes to the "longitude" field. +func (m *LocalityMutation) ResetLongitude() { + m.longitude = nil + m.addlongitude = nil +} + +// SetActive sets the "active" field. +func (m *LocalityMutation) SetActive(b bool) { + m.active = &b +} + +// Active returns the value of the "active" field in the mutation. +func (m *LocalityMutation) Active() (r bool, exists bool) { + v := m.active + if v == nil { + return + } + return *v, true +} + +// OldActive returns the old "active" field's value of the Locality entity. +// If the Locality object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *LocalityMutation) OldActive(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldActive is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldActive requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldActive: %w", err) + } + return oldValue.Active, nil +} + +// ResetActive resets all changes to the "active" field. +func (m *LocalityMutation) ResetActive() { + m.active = nil +} + +// SetCreatedAt sets the "created_at" field. +func (m *LocalityMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *LocalityMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the Locality entity. +// If the Locality object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *LocalityMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *LocalityMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *LocalityMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *LocalityMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the Locality entity. +// If the Locality object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *LocalityMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *LocalityMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// ClearParentParish clears the "parent_parish" edge to the AdministrativeArea entity. +func (m *LocalityMutation) ClearParentParish() { + m.clearedparent_parish = true + m.clearedFields[locality.FieldParentParishID] = struct{}{} +} + +// ParentParishCleared reports if the "parent_parish" edge to the AdministrativeArea entity was cleared. +func (m *LocalityMutation) ParentParishCleared() bool { + return m.clearedparent_parish +} + +// ParentParishIDs returns the "parent_parish" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// ParentParishID instead. It exists only for internal usage by the builders. +func (m *LocalityMutation) ParentParishIDs() (ids []uuid.UUID) { + if id := m.parent_parish; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetParentParish resets all changes to the "parent_parish" edge. +func (m *LocalityMutation) ResetParentParish() { + m.parent_parish = nil + m.clearedparent_parish = false +} + +// AddProviderProfileIDs adds the "provider_profiles" edge to the ProviderProfile entity by ids. +func (m *LocalityMutation) AddProviderProfileIDs(ids ...uuid.UUID) { + if m.provider_profiles == nil { + m.provider_profiles = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.provider_profiles[ids[i]] = struct{}{} + } +} + +// ClearProviderProfiles clears the "provider_profiles" edge to the ProviderProfile entity. +func (m *LocalityMutation) ClearProviderProfiles() { + m.clearedprovider_profiles = true +} + +// ProviderProfilesCleared reports if the "provider_profiles" edge to the ProviderProfile entity was cleared. +func (m *LocalityMutation) ProviderProfilesCleared() bool { + return m.clearedprovider_profiles +} + +// RemoveProviderProfileIDs removes the "provider_profiles" edge to the ProviderProfile entity by IDs. +func (m *LocalityMutation) RemoveProviderProfileIDs(ids ...uuid.UUID) { + if m.removedprovider_profiles == nil { + m.removedprovider_profiles = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.provider_profiles, ids[i]) + m.removedprovider_profiles[ids[i]] = struct{}{} + } +} + +// RemovedProviderProfiles returns the removed IDs of the "provider_profiles" edge to the ProviderProfile entity. +func (m *LocalityMutation) RemovedProviderProfilesIDs() (ids []uuid.UUID) { + for id := range m.removedprovider_profiles { + ids = append(ids, id) + } + return +} + +// ProviderProfilesIDs returns the "provider_profiles" edge IDs in the mutation. +func (m *LocalityMutation) ProviderProfilesIDs() (ids []uuid.UUID) { + for id := range m.provider_profiles { + ids = append(ids, id) + } + return +} + +// ResetProviderProfiles resets all changes to the "provider_profiles" edge. +func (m *LocalityMutation) ResetProviderProfiles() { + m.provider_profiles = nil + m.clearedprovider_profiles = false + m.removedprovider_profiles = nil +} + +// Where appends a list predicates to the LocalityMutation builder. +func (m *LocalityMutation) Where(ps ...predicate.Locality) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the LocalityMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *LocalityMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Locality, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *LocalityMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *LocalityMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (Locality). +func (m *LocalityMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *LocalityMutation) Fields() []string { + fields := make([]string, 0, 12) + if m.slug != nil { + fields = append(fields, locality.FieldSlug) + } + if m.name != nil { + fields = append(fields, locality.FieldName) + } + if m.parent_parish != nil { + fields = append(fields, locality.FieldParentParishID) + } + if m.source != nil { + fields = append(fields, locality.FieldSource) + } + if m.source_element_id != nil { + fields = append(fields, locality.FieldSourceElementID) + } + if m.source_version != nil { + fields = append(fields, locality.FieldSourceVersion) + } + if m.source_retrieved_at != nil { + fields = append(fields, locality.FieldSourceRetrievedAt) + } + if m.latitude != nil { + fields = append(fields, locality.FieldLatitude) + } + if m.longitude != nil { + fields = append(fields, locality.FieldLongitude) + } + if m.active != nil { + fields = append(fields, locality.FieldActive) + } + if m.created_at != nil { + fields = append(fields, locality.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, locality.FieldUpdatedAt) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *LocalityMutation) Field(name string) (ent.Value, bool) { + switch name { + case locality.FieldSlug: + return m.Slug() + case locality.FieldName: + return m.Name() + case locality.FieldParentParishID: + return m.ParentParishID() + case locality.FieldSource: + return m.Source() + case locality.FieldSourceElementID: + return m.SourceElementID() + case locality.FieldSourceVersion: + return m.SourceVersion() + case locality.FieldSourceRetrievedAt: + return m.SourceRetrievedAt() + case locality.FieldLatitude: + return m.Latitude() + case locality.FieldLongitude: + return m.Longitude() + case locality.FieldActive: + return m.Active() + case locality.FieldCreatedAt: + return m.CreatedAt() + case locality.FieldUpdatedAt: + return m.UpdatedAt() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *LocalityMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case locality.FieldSlug: + return m.OldSlug(ctx) + case locality.FieldName: + return m.OldName(ctx) + case locality.FieldParentParishID: + return m.OldParentParishID(ctx) + case locality.FieldSource: + return m.OldSource(ctx) + case locality.FieldSourceElementID: + return m.OldSourceElementID(ctx) + case locality.FieldSourceVersion: + return m.OldSourceVersion(ctx) + case locality.FieldSourceRetrievedAt: + return m.OldSourceRetrievedAt(ctx) + case locality.FieldLatitude: + return m.OldLatitude(ctx) + case locality.FieldLongitude: + return m.OldLongitude(ctx) + case locality.FieldActive: + return m.OldActive(ctx) + case locality.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case locality.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + } + return nil, fmt.Errorf("unknown Locality field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *LocalityMutation) SetField(name string, value ent.Value) error { + switch name { + case locality.FieldSlug: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSlug(v) + return nil + case locality.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + case locality.FieldParentParishID: + v, ok := value.(uuid.UUID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetParentParishID(v) + return nil + case locality.FieldSource: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSource(v) + return nil + case locality.FieldSourceElementID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSourceElementID(v) + return nil + case locality.FieldSourceVersion: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSourceVersion(v) + return nil + case locality.FieldSourceRetrievedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSourceRetrievedAt(v) + return nil + case locality.FieldLatitude: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLatitude(v) + return nil + case locality.FieldLongitude: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLongitude(v) + return nil + case locality.FieldActive: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetActive(v) + return nil + case locality.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case locality.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown Locality field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *LocalityMutation) AddedFields() []string { + var fields []string + if m.addlatitude != nil { + fields = append(fields, locality.FieldLatitude) + } + if m.addlongitude != nil { + fields = append(fields, locality.FieldLongitude) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *LocalityMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case locality.FieldLatitude: + return m.AddedLatitude() + case locality.FieldLongitude: + return m.AddedLongitude() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *LocalityMutation) AddField(name string, value ent.Value) error { + switch name { + case locality.FieldLatitude: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddLatitude(v) + return nil + case locality.FieldLongitude: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddLongitude(v) + return nil + } + return fmt.Errorf("unknown Locality numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *LocalityMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *LocalityMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *LocalityMutation) ClearField(name string) error { + return fmt.Errorf("unknown Locality nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *LocalityMutation) ResetField(name string) error { + switch name { + case locality.FieldSlug: + m.ResetSlug() + return nil + case locality.FieldName: + m.ResetName() + return nil + case locality.FieldParentParishID: + m.ResetParentParishID() + return nil + case locality.FieldSource: + m.ResetSource() + return nil + case locality.FieldSourceElementID: + m.ResetSourceElementID() + return nil + case locality.FieldSourceVersion: + m.ResetSourceVersion() + return nil + case locality.FieldSourceRetrievedAt: + m.ResetSourceRetrievedAt() + return nil + case locality.FieldLatitude: + m.ResetLatitude() + return nil + case locality.FieldLongitude: + m.ResetLongitude() + return nil + case locality.FieldActive: + m.ResetActive() + return nil + case locality.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case locality.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + } + return fmt.Errorf("unknown Locality field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *LocalityMutation) AddedEdges() []string { + edges := make([]string, 0, 2) + if m.parent_parish != nil { + edges = append(edges, locality.EdgeParentParish) + } + if m.provider_profiles != nil { + edges = append(edges, locality.EdgeProviderProfiles) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *LocalityMutation) AddedIDs(name string) []ent.Value { + switch name { + case locality.EdgeParentParish: + if id := m.parent_parish; id != nil { + return []ent.Value{*id} + } + case locality.EdgeProviderProfiles: + ids := make([]ent.Value, 0, len(m.provider_profiles)) + for id := range m.provider_profiles { + ids = append(ids, id) + } + return ids + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *LocalityMutation) RemovedEdges() []string { + edges := make([]string, 0, 2) + if m.removedprovider_profiles != nil { + edges = append(edges, locality.EdgeProviderProfiles) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *LocalityMutation) RemovedIDs(name string) []ent.Value { + switch name { + case locality.EdgeProviderProfiles: + ids := make([]ent.Value, 0, len(m.removedprovider_profiles)) + for id := range m.removedprovider_profiles { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *LocalityMutation) ClearedEdges() []string { + edges := make([]string, 0, 2) + if m.clearedparent_parish { + edges = append(edges, locality.EdgeParentParish) + } + if m.clearedprovider_profiles { + edges = append(edges, locality.EdgeProviderProfiles) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *LocalityMutation) EdgeCleared(name string) bool { + switch name { + case locality.EdgeParentParish: + return m.clearedparent_parish + case locality.EdgeProviderProfiles: + return m.clearedprovider_profiles + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *LocalityMutation) ClearEdge(name string) error { + switch name { + case locality.EdgeParentParish: + m.ClearParentParish() + return nil + } + return fmt.Errorf("unknown Locality unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *LocalityMutation) ResetEdge(name string) error { + switch name { + case locality.EdgeParentParish: + m.ResetParentParish() + return nil + case locality.EdgeProviderProfiles: + m.ResetProviderProfiles() + return nil + } + return fmt.Errorf("unknown Locality edge %s", name) +} + +// ProviderProfileMutation represents an operation that mutates the ProviderProfile nodes in the graph. +type ProviderProfileMutation struct { + config + op Op + typ string + id *uuid.UUID + display_name *string + provider_type *string + bio *string + primary_locality_id *uuid.UUID + max_travel_distance_km *int + addmax_travel_distance_km *int + travels_to_customer *bool + receives_customer *bool + remote_services *bool + created_at *time.Time + updated_at *time.Time + clearedFields map[string]struct{} + service_localities map[uuid.UUID]struct{} + removedservice_localities map[uuid.UUID]struct{} + clearedservice_localities bool + spoken_languages map[string]struct{} + removedspoken_languages map[string]struct{} + clearedspoken_languages bool + done bool + oldValue func(context.Context) (*ProviderProfile, error) + predicates []predicate.ProviderProfile +} + +var _ ent.Mutation = (*ProviderProfileMutation)(nil) + +// providerprofileOption allows management of the mutation configuration using functional options. +type providerprofileOption func(*ProviderProfileMutation) + +// newProviderProfileMutation creates new mutation for the ProviderProfile entity. +func newProviderProfileMutation(c config, op Op, opts ...providerprofileOption) *ProviderProfileMutation { + m := &ProviderProfileMutation{ + config: c, + op: op, + typ: TypeProviderProfile, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withProviderProfileID sets the ID field of the mutation. +func withProviderProfileID(id uuid.UUID) providerprofileOption { + return func(m *ProviderProfileMutation) { + var ( + err error + once sync.Once + value *ProviderProfile + ) + m.oldValue = func(ctx context.Context) (*ProviderProfile, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().ProviderProfile.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withProviderProfile sets the old ProviderProfile of the mutation. +func withProviderProfile(node *ProviderProfile) providerprofileOption { + return func(m *ProviderProfileMutation) { + m.oldValue = func(context.Context) (*ProviderProfile, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m ProviderProfileMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m ProviderProfileMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of ProviderProfile entities. +func (m *ProviderProfileMutation) SetID(id uuid.UUID) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *ProviderProfileMutation) ID() (id uuid.UUID, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *ProviderProfileMutation) IDs(ctx context.Context) ([]uuid.UUID, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []uuid.UUID{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().ProviderProfile.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetDisplayName sets the "display_name" field. +func (m *ProviderProfileMutation) SetDisplayName(s string) { + m.display_name = &s +} + +// DisplayName returns the value of the "display_name" field in the mutation. +func (m *ProviderProfileMutation) DisplayName() (r string, exists bool) { + v := m.display_name + if v == nil { + return + } + return *v, true +} + +// OldDisplayName returns the old "display_name" field's value of the ProviderProfile entity. +// If the ProviderProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ProviderProfileMutation) OldDisplayName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldDisplayName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldDisplayName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldDisplayName: %w", err) + } + return oldValue.DisplayName, nil +} + +// ResetDisplayName resets all changes to the "display_name" field. +func (m *ProviderProfileMutation) ResetDisplayName() { + m.display_name = nil +} + +// SetProviderType sets the "provider_type" field. +func (m *ProviderProfileMutation) SetProviderType(s string) { + m.provider_type = &s +} + +// ProviderType returns the value of the "provider_type" field in the mutation. +func (m *ProviderProfileMutation) ProviderType() (r string, exists bool) { + v := m.provider_type + if v == nil { + return + } + return *v, true +} + +// OldProviderType returns the old "provider_type" field's value of the ProviderProfile entity. +// If the ProviderProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ProviderProfileMutation) OldProviderType(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldProviderType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldProviderType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldProviderType: %w", err) + } + return oldValue.ProviderType, nil +} + +// ResetProviderType resets all changes to the "provider_type" field. +func (m *ProviderProfileMutation) ResetProviderType() { + m.provider_type = nil +} + +// SetBio sets the "bio" field. +func (m *ProviderProfileMutation) SetBio(s string) { + m.bio = &s +} + +// Bio returns the value of the "bio" field in the mutation. +func (m *ProviderProfileMutation) Bio() (r string, exists bool) { + v := m.bio + if v == nil { + return + } + return *v, true +} + +// OldBio returns the old "bio" field's value of the ProviderProfile entity. +// If the ProviderProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ProviderProfileMutation) OldBio(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldBio is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldBio requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldBio: %w", err) + } + return oldValue.Bio, nil +} + +// ResetBio resets all changes to the "bio" field. +func (m *ProviderProfileMutation) ResetBio() { + m.bio = nil +} + +// SetPrimaryLocalityID sets the "primary_locality_id" field. +func (m *ProviderProfileMutation) SetPrimaryLocalityID(u uuid.UUID) { + m.primary_locality_id = &u +} + +// PrimaryLocalityID returns the value of the "primary_locality_id" field in the mutation. +func (m *ProviderProfileMutation) PrimaryLocalityID() (r uuid.UUID, exists bool) { + v := m.primary_locality_id + if v == nil { + return + } + return *v, true +} + +// OldPrimaryLocalityID returns the old "primary_locality_id" field's value of the ProviderProfile entity. +// If the ProviderProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ProviderProfileMutation) OldPrimaryLocalityID(ctx context.Context) (v uuid.UUID, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPrimaryLocalityID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPrimaryLocalityID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPrimaryLocalityID: %w", err) + } + return oldValue.PrimaryLocalityID, nil +} + +// ResetPrimaryLocalityID resets all changes to the "primary_locality_id" field. +func (m *ProviderProfileMutation) ResetPrimaryLocalityID() { + m.primary_locality_id = nil +} + +// SetMaxTravelDistanceKm sets the "max_travel_distance_km" field. +func (m *ProviderProfileMutation) SetMaxTravelDistanceKm(i int) { + m.max_travel_distance_km = &i + m.addmax_travel_distance_km = nil +} + +// MaxTravelDistanceKm returns the value of the "max_travel_distance_km" field in the mutation. +func (m *ProviderProfileMutation) MaxTravelDistanceKm() (r int, exists bool) { + v := m.max_travel_distance_km + if v == nil { + return + } + return *v, true +} + +// OldMaxTravelDistanceKm returns the old "max_travel_distance_km" field's value of the ProviderProfile entity. +// If the ProviderProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ProviderProfileMutation) OldMaxTravelDistanceKm(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMaxTravelDistanceKm is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMaxTravelDistanceKm requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMaxTravelDistanceKm: %w", err) + } + return oldValue.MaxTravelDistanceKm, nil +} + +// AddMaxTravelDistanceKm adds i to the "max_travel_distance_km" field. +func (m *ProviderProfileMutation) AddMaxTravelDistanceKm(i int) { + if m.addmax_travel_distance_km != nil { + *m.addmax_travel_distance_km += i + } else { + m.addmax_travel_distance_km = &i + } +} + +// AddedMaxTravelDistanceKm returns the value that was added to the "max_travel_distance_km" field in this mutation. +func (m *ProviderProfileMutation) AddedMaxTravelDistanceKm() (r int, exists bool) { + v := m.addmax_travel_distance_km + if v == nil { + return + } + return *v, true +} + +// ResetMaxTravelDistanceKm resets all changes to the "max_travel_distance_km" field. +func (m *ProviderProfileMutation) ResetMaxTravelDistanceKm() { + m.max_travel_distance_km = nil + m.addmax_travel_distance_km = nil +} + +// SetTravelsToCustomer sets the "travels_to_customer" field. +func (m *ProviderProfileMutation) SetTravelsToCustomer(b bool) { + m.travels_to_customer = &b +} + +// TravelsToCustomer returns the value of the "travels_to_customer" field in the mutation. +func (m *ProviderProfileMutation) TravelsToCustomer() (r bool, exists bool) { + v := m.travels_to_customer + if v == nil { + return + } + return *v, true +} + +// OldTravelsToCustomer returns the old "travels_to_customer" field's value of the ProviderProfile entity. +// If the ProviderProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ProviderProfileMutation) OldTravelsToCustomer(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTravelsToCustomer is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTravelsToCustomer requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTravelsToCustomer: %w", err) + } + return oldValue.TravelsToCustomer, nil +} + +// ResetTravelsToCustomer resets all changes to the "travels_to_customer" field. +func (m *ProviderProfileMutation) ResetTravelsToCustomer() { + m.travels_to_customer = nil +} + +// SetReceivesCustomer sets the "receives_customer" field. +func (m *ProviderProfileMutation) SetReceivesCustomer(b bool) { + m.receives_customer = &b +} + +// ReceivesCustomer returns the value of the "receives_customer" field in the mutation. +func (m *ProviderProfileMutation) ReceivesCustomer() (r bool, exists bool) { + v := m.receives_customer + if v == nil { + return + } + return *v, true +} + +// OldReceivesCustomer returns the old "receives_customer" field's value of the ProviderProfile entity. +// If the ProviderProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ProviderProfileMutation) OldReceivesCustomer(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReceivesCustomer is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReceivesCustomer requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReceivesCustomer: %w", err) + } + return oldValue.ReceivesCustomer, nil +} + +// ResetReceivesCustomer resets all changes to the "receives_customer" field. +func (m *ProviderProfileMutation) ResetReceivesCustomer() { + m.receives_customer = nil +} + +// SetRemoteServices sets the "remote_services" field. +func (m *ProviderProfileMutation) SetRemoteServices(b bool) { + m.remote_services = &b +} + +// RemoteServices returns the value of the "remote_services" field in the mutation. +func (m *ProviderProfileMutation) RemoteServices() (r bool, exists bool) { + v := m.remote_services + if v == nil { + return + } + return *v, true +} + +// OldRemoteServices returns the old "remote_services" field's value of the ProviderProfile entity. +// If the ProviderProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ProviderProfileMutation) OldRemoteServices(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRemoteServices is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRemoteServices requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRemoteServices: %w", err) + } + return oldValue.RemoteServices, nil +} + +// ResetRemoteServices resets all changes to the "remote_services" field. +func (m *ProviderProfileMutation) ResetRemoteServices() { + m.remote_services = nil +} + +// SetCreatedAt sets the "created_at" field. +func (m *ProviderProfileMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *ProviderProfileMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the ProviderProfile entity. +// If the ProviderProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ProviderProfileMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *ProviderProfileMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *ProviderProfileMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *ProviderProfileMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the ProviderProfile entity. +// If the ProviderProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ProviderProfileMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *ProviderProfileMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// AddServiceLocalityIDs adds the "service_localities" edge to the Locality entity by ids. +func (m *ProviderProfileMutation) AddServiceLocalityIDs(ids ...uuid.UUID) { + if m.service_localities == nil { + m.service_localities = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.service_localities[ids[i]] = struct{}{} + } +} + +// ClearServiceLocalities clears the "service_localities" edge to the Locality entity. +func (m *ProviderProfileMutation) ClearServiceLocalities() { + m.clearedservice_localities = true +} + +// ServiceLocalitiesCleared reports if the "service_localities" edge to the Locality entity was cleared. +func (m *ProviderProfileMutation) ServiceLocalitiesCleared() bool { + return m.clearedservice_localities +} + +// RemoveServiceLocalityIDs removes the "service_localities" edge to the Locality entity by IDs. +func (m *ProviderProfileMutation) RemoveServiceLocalityIDs(ids ...uuid.UUID) { + if m.removedservice_localities == nil { + m.removedservice_localities = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.service_localities, ids[i]) + m.removedservice_localities[ids[i]] = struct{}{} + } +} + +// RemovedServiceLocalities returns the removed IDs of the "service_localities" edge to the Locality entity. +func (m *ProviderProfileMutation) RemovedServiceLocalitiesIDs() (ids []uuid.UUID) { + for id := range m.removedservice_localities { + ids = append(ids, id) + } + return +} + +// ServiceLocalitiesIDs returns the "service_localities" edge IDs in the mutation. +func (m *ProviderProfileMutation) ServiceLocalitiesIDs() (ids []uuid.UUID) { + for id := range m.service_localities { + ids = append(ids, id) + } + return +} + +// ResetServiceLocalities resets all changes to the "service_localities" edge. +func (m *ProviderProfileMutation) ResetServiceLocalities() { + m.service_localities = nil + m.clearedservice_localities = false + m.removedservice_localities = nil +} + +// AddSpokenLanguageIDs adds the "spoken_languages" edge to the SpokenLanguage entity by ids. +func (m *ProviderProfileMutation) AddSpokenLanguageIDs(ids ...string) { + if m.spoken_languages == nil { + m.spoken_languages = make(map[string]struct{}) + } + for i := range ids { + m.spoken_languages[ids[i]] = struct{}{} + } +} + +// ClearSpokenLanguages clears the "spoken_languages" edge to the SpokenLanguage entity. +func (m *ProviderProfileMutation) ClearSpokenLanguages() { + m.clearedspoken_languages = true +} + +// SpokenLanguagesCleared reports if the "spoken_languages" edge to the SpokenLanguage entity was cleared. +func (m *ProviderProfileMutation) SpokenLanguagesCleared() bool { + return m.clearedspoken_languages +} + +// RemoveSpokenLanguageIDs removes the "spoken_languages" edge to the SpokenLanguage entity by IDs. +func (m *ProviderProfileMutation) RemoveSpokenLanguageIDs(ids ...string) { + if m.removedspoken_languages == nil { + m.removedspoken_languages = make(map[string]struct{}) + } + for i := range ids { + delete(m.spoken_languages, ids[i]) + m.removedspoken_languages[ids[i]] = struct{}{} + } +} + +// RemovedSpokenLanguages returns the removed IDs of the "spoken_languages" edge to the SpokenLanguage entity. +func (m *ProviderProfileMutation) RemovedSpokenLanguagesIDs() (ids []string) { + for id := range m.removedspoken_languages { + ids = append(ids, id) + } + return +} + +// SpokenLanguagesIDs returns the "spoken_languages" edge IDs in the mutation. +func (m *ProviderProfileMutation) SpokenLanguagesIDs() (ids []string) { + for id := range m.spoken_languages { + ids = append(ids, id) + } + return +} + +// ResetSpokenLanguages resets all changes to the "spoken_languages" edge. +func (m *ProviderProfileMutation) ResetSpokenLanguages() { + m.spoken_languages = nil + m.clearedspoken_languages = false + m.removedspoken_languages = nil +} + +// Where appends a list predicates to the ProviderProfileMutation builder. +func (m *ProviderProfileMutation) Where(ps ...predicate.ProviderProfile) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the ProviderProfileMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *ProviderProfileMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.ProviderProfile, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *ProviderProfileMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *ProviderProfileMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (ProviderProfile). +func (m *ProviderProfileMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *ProviderProfileMutation) Fields() []string { + fields := make([]string, 0, 10) + if m.display_name != nil { + fields = append(fields, providerprofile.FieldDisplayName) + } + if m.provider_type != nil { + fields = append(fields, providerprofile.FieldProviderType) + } + if m.bio != nil { + fields = append(fields, providerprofile.FieldBio) + } + if m.primary_locality_id != nil { + fields = append(fields, providerprofile.FieldPrimaryLocalityID) + } + if m.max_travel_distance_km != nil { + fields = append(fields, providerprofile.FieldMaxTravelDistanceKm) + } + if m.travels_to_customer != nil { + fields = append(fields, providerprofile.FieldTravelsToCustomer) + } + if m.receives_customer != nil { + fields = append(fields, providerprofile.FieldReceivesCustomer) + } + if m.remote_services != nil { + fields = append(fields, providerprofile.FieldRemoteServices) + } + if m.created_at != nil { + fields = append(fields, providerprofile.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, providerprofile.FieldUpdatedAt) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *ProviderProfileMutation) Field(name string) (ent.Value, bool) { + switch name { + case providerprofile.FieldDisplayName: + return m.DisplayName() + case providerprofile.FieldProviderType: + return m.ProviderType() + case providerprofile.FieldBio: + return m.Bio() + case providerprofile.FieldPrimaryLocalityID: + return m.PrimaryLocalityID() + case providerprofile.FieldMaxTravelDistanceKm: + return m.MaxTravelDistanceKm() + case providerprofile.FieldTravelsToCustomer: + return m.TravelsToCustomer() + case providerprofile.FieldReceivesCustomer: + return m.ReceivesCustomer() + case providerprofile.FieldRemoteServices: + return m.RemoteServices() + case providerprofile.FieldCreatedAt: + return m.CreatedAt() + case providerprofile.FieldUpdatedAt: + return m.UpdatedAt() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *ProviderProfileMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case providerprofile.FieldDisplayName: + return m.OldDisplayName(ctx) + case providerprofile.FieldProviderType: + return m.OldProviderType(ctx) + case providerprofile.FieldBio: + return m.OldBio(ctx) + case providerprofile.FieldPrimaryLocalityID: + return m.OldPrimaryLocalityID(ctx) + case providerprofile.FieldMaxTravelDistanceKm: + return m.OldMaxTravelDistanceKm(ctx) + case providerprofile.FieldTravelsToCustomer: + return m.OldTravelsToCustomer(ctx) + case providerprofile.FieldReceivesCustomer: + return m.OldReceivesCustomer(ctx) + case providerprofile.FieldRemoteServices: + return m.OldRemoteServices(ctx) + case providerprofile.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case providerprofile.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + } + return nil, fmt.Errorf("unknown ProviderProfile field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ProviderProfileMutation) SetField(name string, value ent.Value) error { + switch name { + case providerprofile.FieldDisplayName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetDisplayName(v) + return nil + case providerprofile.FieldProviderType: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetProviderType(v) + return nil + case providerprofile.FieldBio: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetBio(v) + return nil + case providerprofile.FieldPrimaryLocalityID: + v, ok := value.(uuid.UUID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPrimaryLocalityID(v) + return nil + case providerprofile.FieldMaxTravelDistanceKm: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMaxTravelDistanceKm(v) + return nil + case providerprofile.FieldTravelsToCustomer: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTravelsToCustomer(v) + return nil + case providerprofile.FieldReceivesCustomer: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReceivesCustomer(v) + return nil + case providerprofile.FieldRemoteServices: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRemoteServices(v) + return nil + case providerprofile.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case providerprofile.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown ProviderProfile field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *ProviderProfileMutation) AddedFields() []string { + var fields []string + if m.addmax_travel_distance_km != nil { + fields = append(fields, providerprofile.FieldMaxTravelDistanceKm) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *ProviderProfileMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case providerprofile.FieldMaxTravelDistanceKm: + return m.AddedMaxTravelDistanceKm() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ProviderProfileMutation) AddField(name string, value ent.Value) error { + switch name { + case providerprofile.FieldMaxTravelDistanceKm: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddMaxTravelDistanceKm(v) + return nil + } + return fmt.Errorf("unknown ProviderProfile numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *ProviderProfileMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *ProviderProfileMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *ProviderProfileMutation) ClearField(name string) error { + return fmt.Errorf("unknown ProviderProfile nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *ProviderProfileMutation) ResetField(name string) error { + switch name { + case providerprofile.FieldDisplayName: + m.ResetDisplayName() + return nil + case providerprofile.FieldProviderType: + m.ResetProviderType() + return nil + case providerprofile.FieldBio: + m.ResetBio() + return nil + case providerprofile.FieldPrimaryLocalityID: + m.ResetPrimaryLocalityID() + return nil + case providerprofile.FieldMaxTravelDistanceKm: + m.ResetMaxTravelDistanceKm() + return nil + case providerprofile.FieldTravelsToCustomer: + m.ResetTravelsToCustomer() + return nil + case providerprofile.FieldReceivesCustomer: + m.ResetReceivesCustomer() + return nil + case providerprofile.FieldRemoteServices: + m.ResetRemoteServices() + return nil + case providerprofile.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case providerprofile.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + } + return fmt.Errorf("unknown ProviderProfile field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *ProviderProfileMutation) AddedEdges() []string { + edges := make([]string, 0, 2) + if m.service_localities != nil { + edges = append(edges, providerprofile.EdgeServiceLocalities) + } + if m.spoken_languages != nil { + edges = append(edges, providerprofile.EdgeSpokenLanguages) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *ProviderProfileMutation) AddedIDs(name string) []ent.Value { + switch name { + case providerprofile.EdgeServiceLocalities: + ids := make([]ent.Value, 0, len(m.service_localities)) + for id := range m.service_localities { + ids = append(ids, id) + } + return ids + case providerprofile.EdgeSpokenLanguages: + ids := make([]ent.Value, 0, len(m.spoken_languages)) + for id := range m.spoken_languages { + ids = append(ids, id) + } + return ids + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *ProviderProfileMutation) RemovedEdges() []string { + edges := make([]string, 0, 2) + if m.removedservice_localities != nil { + edges = append(edges, providerprofile.EdgeServiceLocalities) + } + if m.removedspoken_languages != nil { + edges = append(edges, providerprofile.EdgeSpokenLanguages) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *ProviderProfileMutation) RemovedIDs(name string) []ent.Value { + switch name { + case providerprofile.EdgeServiceLocalities: + ids := make([]ent.Value, 0, len(m.removedservice_localities)) + for id := range m.removedservice_localities { + ids = append(ids, id) + } + return ids + case providerprofile.EdgeSpokenLanguages: + ids := make([]ent.Value, 0, len(m.removedspoken_languages)) + for id := range m.removedspoken_languages { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *ProviderProfileMutation) ClearedEdges() []string { + edges := make([]string, 0, 2) + if m.clearedservice_localities { + edges = append(edges, providerprofile.EdgeServiceLocalities) + } + if m.clearedspoken_languages { + edges = append(edges, providerprofile.EdgeSpokenLanguages) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *ProviderProfileMutation) EdgeCleared(name string) bool { + switch name { + case providerprofile.EdgeServiceLocalities: + return m.clearedservice_localities + case providerprofile.EdgeSpokenLanguages: + return m.clearedspoken_languages + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *ProviderProfileMutation) ClearEdge(name string) error { + switch name { + } + return fmt.Errorf("unknown ProviderProfile unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *ProviderProfileMutation) ResetEdge(name string) error { + switch name { + case providerprofile.EdgeServiceLocalities: + m.ResetServiceLocalities() + return nil + case providerprofile.EdgeSpokenLanguages: + m.ResetSpokenLanguages() + return nil + } + return fmt.Errorf("unknown ProviderProfile edge %s", name) +} + +// ProviderServiceLocalityMutation represents an operation that mutates the ProviderServiceLocality nodes in the graph. +type ProviderServiceLocalityMutation struct { + config + op Op + typ string + clearedFields map[string]struct{} + profile *uuid.UUID + clearedprofile bool + locality *uuid.UUID + clearedlocality bool + done bool + oldValue func(context.Context) (*ProviderServiceLocality, error) + predicates []predicate.ProviderServiceLocality +} + +var _ ent.Mutation = (*ProviderServiceLocalityMutation)(nil) + +// providerservicelocalityOption allows management of the mutation configuration using functional options. +type providerservicelocalityOption func(*ProviderServiceLocalityMutation) + +// newProviderServiceLocalityMutation creates new mutation for the ProviderServiceLocality entity. +func newProviderServiceLocalityMutation(c config, op Op, opts ...providerservicelocalityOption) *ProviderServiceLocalityMutation { + m := &ProviderServiceLocalityMutation{ + config: c, + op: op, + typ: TypeProviderServiceLocality, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m ProviderServiceLocalityMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m ProviderServiceLocalityMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetInternalUserID sets the "internal_user_id" field. +func (m *ProviderServiceLocalityMutation) SetInternalUserID(u uuid.UUID) { + m.profile = &u +} + +// InternalUserID returns the value of the "internal_user_id" field in the mutation. +func (m *ProviderServiceLocalityMutation) InternalUserID() (r uuid.UUID, exists bool) { + v := m.profile + if v == nil { + return + } + return *v, true +} + +// ResetInternalUserID resets all changes to the "internal_user_id" field. +func (m *ProviderServiceLocalityMutation) ResetInternalUserID() { + m.profile = nil +} + +// SetLocalityID sets the "locality_id" field. +func (m *ProviderServiceLocalityMutation) SetLocalityID(u uuid.UUID) { + m.locality = &u +} + +// LocalityID returns the value of the "locality_id" field in the mutation. +func (m *ProviderServiceLocalityMutation) LocalityID() (r uuid.UUID, exists bool) { + v := m.locality + if v == nil { + return + } + return *v, true +} + +// ResetLocalityID resets all changes to the "locality_id" field. +func (m *ProviderServiceLocalityMutation) ResetLocalityID() { + m.locality = nil +} + +// SetProfileID sets the "profile" edge to the ProviderProfile entity by id. +func (m *ProviderServiceLocalityMutation) SetProfileID(id uuid.UUID) { + m.profile = &id +} + +// ClearProfile clears the "profile" edge to the ProviderProfile entity. +func (m *ProviderServiceLocalityMutation) ClearProfile() { + m.clearedprofile = true + m.clearedFields[providerservicelocality.FieldInternalUserID] = struct{}{} +} + +// ProfileCleared reports if the "profile" edge to the ProviderProfile entity was cleared. +func (m *ProviderServiceLocalityMutation) ProfileCleared() bool { + return m.clearedprofile +} + +// ProfileID returns the "profile" edge ID in the mutation. +func (m *ProviderServiceLocalityMutation) ProfileID() (id uuid.UUID, exists bool) { + if m.profile != nil { + return *m.profile, true + } + return +} + +// ProfileIDs returns the "profile" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// ProfileID instead. It exists only for internal usage by the builders. +func (m *ProviderServiceLocalityMutation) ProfileIDs() (ids []uuid.UUID) { + if id := m.profile; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetProfile resets all changes to the "profile" edge. +func (m *ProviderServiceLocalityMutation) ResetProfile() { + m.profile = nil + m.clearedprofile = false +} + +// ClearLocality clears the "locality" edge to the Locality entity. +func (m *ProviderServiceLocalityMutation) ClearLocality() { + m.clearedlocality = true + m.clearedFields[providerservicelocality.FieldLocalityID] = struct{}{} +} + +// LocalityCleared reports if the "locality" edge to the Locality entity was cleared. +func (m *ProviderServiceLocalityMutation) LocalityCleared() bool { + return m.clearedlocality +} + +// LocalityIDs returns the "locality" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// LocalityID instead. It exists only for internal usage by the builders. +func (m *ProviderServiceLocalityMutation) LocalityIDs() (ids []uuid.UUID) { + if id := m.locality; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetLocality resets all changes to the "locality" edge. +func (m *ProviderServiceLocalityMutation) ResetLocality() { + m.locality = nil + m.clearedlocality = false +} + +// Where appends a list predicates to the ProviderServiceLocalityMutation builder. +func (m *ProviderServiceLocalityMutation) Where(ps ...predicate.ProviderServiceLocality) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the ProviderServiceLocalityMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *ProviderServiceLocalityMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.ProviderServiceLocality, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *ProviderServiceLocalityMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *ProviderServiceLocalityMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (ProviderServiceLocality). +func (m *ProviderServiceLocalityMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *ProviderServiceLocalityMutation) Fields() []string { + fields := make([]string, 0, 2) + if m.profile != nil { + fields = append(fields, providerservicelocality.FieldInternalUserID) + } + if m.locality != nil { + fields = append(fields, providerservicelocality.FieldLocalityID) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *ProviderServiceLocalityMutation) Field(name string) (ent.Value, bool) { + switch name { + case providerservicelocality.FieldInternalUserID: + return m.InternalUserID() + case providerservicelocality.FieldLocalityID: + return m.LocalityID() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *ProviderServiceLocalityMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + return nil, errors.New("edge schema ProviderServiceLocality does not support getting old values") +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ProviderServiceLocalityMutation) SetField(name string, value ent.Value) error { + switch name { + case providerservicelocality.FieldInternalUserID: + v, ok := value.(uuid.UUID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetInternalUserID(v) + return nil + case providerservicelocality.FieldLocalityID: + v, ok := value.(uuid.UUID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLocalityID(v) + return nil + } + return fmt.Errorf("unknown ProviderServiceLocality field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *ProviderServiceLocalityMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *ProviderServiceLocalityMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ProviderServiceLocalityMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown ProviderServiceLocality numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *ProviderServiceLocalityMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *ProviderServiceLocalityMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *ProviderServiceLocalityMutation) ClearField(name string) error { + return fmt.Errorf("unknown ProviderServiceLocality nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *ProviderServiceLocalityMutation) ResetField(name string) error { + switch name { + case providerservicelocality.FieldInternalUserID: + m.ResetInternalUserID() + return nil + case providerservicelocality.FieldLocalityID: + m.ResetLocalityID() + return nil + } + return fmt.Errorf("unknown ProviderServiceLocality field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *ProviderServiceLocalityMutation) AddedEdges() []string { + edges := make([]string, 0, 2) + if m.profile != nil { + edges = append(edges, providerservicelocality.EdgeProfile) + } + if m.locality != nil { + edges = append(edges, providerservicelocality.EdgeLocality) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *ProviderServiceLocalityMutation) AddedIDs(name string) []ent.Value { + switch name { + case providerservicelocality.EdgeProfile: + if id := m.profile; id != nil { + return []ent.Value{*id} + } + case providerservicelocality.EdgeLocality: + if id := m.locality; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *ProviderServiceLocalityMutation) RemovedEdges() []string { + edges := make([]string, 0, 2) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *ProviderServiceLocalityMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *ProviderServiceLocalityMutation) ClearedEdges() []string { + edges := make([]string, 0, 2) + if m.clearedprofile { + edges = append(edges, providerservicelocality.EdgeProfile) + } + if m.clearedlocality { + edges = append(edges, providerservicelocality.EdgeLocality) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *ProviderServiceLocalityMutation) EdgeCleared(name string) bool { + switch name { + case providerservicelocality.EdgeProfile: + return m.clearedprofile + case providerservicelocality.EdgeLocality: + return m.clearedlocality + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *ProviderServiceLocalityMutation) ClearEdge(name string) error { + switch name { + case providerservicelocality.EdgeProfile: + m.ClearProfile() + return nil + case providerservicelocality.EdgeLocality: + m.ClearLocality() + return nil + } + return fmt.Errorf("unknown ProviderServiceLocality unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *ProviderServiceLocalityMutation) ResetEdge(name string) error { + switch name { + case providerservicelocality.EdgeProfile: + m.ResetProfile() + return nil + case providerservicelocality.EdgeLocality: + m.ResetLocality() + return nil + } + return fmt.Errorf("unknown ProviderServiceLocality edge %s", name) +} + +// ProviderSpokenLanguageMutation represents an operation that mutates the ProviderSpokenLanguage nodes in the graph. +type ProviderSpokenLanguageMutation struct { + config + op Op + typ string + clearedFields map[string]struct{} + profile *uuid.UUID + clearedprofile bool + language *string + clearedlanguage bool + done bool + oldValue func(context.Context) (*ProviderSpokenLanguage, error) + predicates []predicate.ProviderSpokenLanguage +} + +var _ ent.Mutation = (*ProviderSpokenLanguageMutation)(nil) + +// providerspokenlanguageOption allows management of the mutation configuration using functional options. +type providerspokenlanguageOption func(*ProviderSpokenLanguageMutation) + +// newProviderSpokenLanguageMutation creates new mutation for the ProviderSpokenLanguage entity. +func newProviderSpokenLanguageMutation(c config, op Op, opts ...providerspokenlanguageOption) *ProviderSpokenLanguageMutation { + m := &ProviderSpokenLanguageMutation{ + config: c, + op: op, + typ: TypeProviderSpokenLanguage, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m ProviderSpokenLanguageMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m ProviderSpokenLanguageMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetInternalUserID sets the "internal_user_id" field. +func (m *ProviderSpokenLanguageMutation) SetInternalUserID(u uuid.UUID) { + m.profile = &u +} + +// InternalUserID returns the value of the "internal_user_id" field in the mutation. +func (m *ProviderSpokenLanguageMutation) InternalUserID() (r uuid.UUID, exists bool) { + v := m.profile + if v == nil { + return + } + return *v, true +} + +// ResetInternalUserID resets all changes to the "internal_user_id" field. +func (m *ProviderSpokenLanguageMutation) ResetInternalUserID() { + m.profile = nil +} + +// SetLanguageCode sets the "language_code" field. +func (m *ProviderSpokenLanguageMutation) SetLanguageCode(s string) { + m.language = &s +} + +// LanguageCode returns the value of the "language_code" field in the mutation. +func (m *ProviderSpokenLanguageMutation) LanguageCode() (r string, exists bool) { + v := m.language + if v == nil { + return + } + return *v, true +} + +// ResetLanguageCode resets all changes to the "language_code" field. +func (m *ProviderSpokenLanguageMutation) ResetLanguageCode() { + m.language = nil +} + +// SetProfileID sets the "profile" edge to the ProviderProfile entity by id. +func (m *ProviderSpokenLanguageMutation) SetProfileID(id uuid.UUID) { + m.profile = &id +} + +// ClearProfile clears the "profile" edge to the ProviderProfile entity. +func (m *ProviderSpokenLanguageMutation) ClearProfile() { + m.clearedprofile = true + m.clearedFields[providerspokenlanguage.FieldInternalUserID] = struct{}{} +} + +// ProfileCleared reports if the "profile" edge to the ProviderProfile entity was cleared. +func (m *ProviderSpokenLanguageMutation) ProfileCleared() bool { + return m.clearedprofile +} + +// ProfileID returns the "profile" edge ID in the mutation. +func (m *ProviderSpokenLanguageMutation) ProfileID() (id uuid.UUID, exists bool) { + if m.profile != nil { + return *m.profile, true + } + return +} + +// ProfileIDs returns the "profile" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// ProfileID instead. It exists only for internal usage by the builders. +func (m *ProviderSpokenLanguageMutation) ProfileIDs() (ids []uuid.UUID) { + if id := m.profile; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetProfile resets all changes to the "profile" edge. +func (m *ProviderSpokenLanguageMutation) ResetProfile() { + m.profile = nil + m.clearedprofile = false +} + +// SetLanguageID sets the "language" edge to the SpokenLanguage entity by id. +func (m *ProviderSpokenLanguageMutation) SetLanguageID(id string) { + m.language = &id +} + +// ClearLanguage clears the "language" edge to the SpokenLanguage entity. +func (m *ProviderSpokenLanguageMutation) ClearLanguage() { + m.clearedlanguage = true + m.clearedFields[providerspokenlanguage.FieldLanguageCode] = struct{}{} +} + +// LanguageCleared reports if the "language" edge to the SpokenLanguage entity was cleared. +func (m *ProviderSpokenLanguageMutation) LanguageCleared() bool { + return m.clearedlanguage +} + +// LanguageID returns the "language" edge ID in the mutation. +func (m *ProviderSpokenLanguageMutation) LanguageID() (id string, exists bool) { + if m.language != nil { + return *m.language, true + } + return +} + +// LanguageIDs returns the "language" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// LanguageID instead. It exists only for internal usage by the builders. +func (m *ProviderSpokenLanguageMutation) LanguageIDs() (ids []string) { + if id := m.language; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetLanguage resets all changes to the "language" edge. +func (m *ProviderSpokenLanguageMutation) ResetLanguage() { + m.language = nil + m.clearedlanguage = false +} + +// Where appends a list predicates to the ProviderSpokenLanguageMutation builder. +func (m *ProviderSpokenLanguageMutation) Where(ps ...predicate.ProviderSpokenLanguage) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the ProviderSpokenLanguageMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *ProviderSpokenLanguageMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.ProviderSpokenLanguage, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *ProviderSpokenLanguageMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *ProviderSpokenLanguageMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (ProviderSpokenLanguage). +func (m *ProviderSpokenLanguageMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *ProviderSpokenLanguageMutation) Fields() []string { + fields := make([]string, 0, 2) + if m.profile != nil { + fields = append(fields, providerspokenlanguage.FieldInternalUserID) + } + if m.language != nil { + fields = append(fields, providerspokenlanguage.FieldLanguageCode) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *ProviderSpokenLanguageMutation) Field(name string) (ent.Value, bool) { + switch name { + case providerspokenlanguage.FieldInternalUserID: + return m.InternalUserID() + case providerspokenlanguage.FieldLanguageCode: + return m.LanguageCode() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *ProviderSpokenLanguageMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + return nil, errors.New("edge schema ProviderSpokenLanguage does not support getting old values") +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ProviderSpokenLanguageMutation) SetField(name string, value ent.Value) error { + switch name { + case providerspokenlanguage.FieldInternalUserID: + v, ok := value.(uuid.UUID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetInternalUserID(v) + return nil + case providerspokenlanguage.FieldLanguageCode: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLanguageCode(v) + return nil + } + return fmt.Errorf("unknown ProviderSpokenLanguage field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *ProviderSpokenLanguageMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *ProviderSpokenLanguageMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ProviderSpokenLanguageMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown ProviderSpokenLanguage numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *ProviderSpokenLanguageMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *ProviderSpokenLanguageMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *ProviderSpokenLanguageMutation) ClearField(name string) error { + return fmt.Errorf("unknown ProviderSpokenLanguage nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *ProviderSpokenLanguageMutation) ResetField(name string) error { + switch name { + case providerspokenlanguage.FieldInternalUserID: + m.ResetInternalUserID() + return nil + case providerspokenlanguage.FieldLanguageCode: + m.ResetLanguageCode() + return nil + } + return fmt.Errorf("unknown ProviderSpokenLanguage field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *ProviderSpokenLanguageMutation) AddedEdges() []string { + edges := make([]string, 0, 2) + if m.profile != nil { + edges = append(edges, providerspokenlanguage.EdgeProfile) + } + if m.language != nil { + edges = append(edges, providerspokenlanguage.EdgeLanguage) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *ProviderSpokenLanguageMutation) AddedIDs(name string) []ent.Value { + switch name { + case providerspokenlanguage.EdgeProfile: + if id := m.profile; id != nil { + return []ent.Value{*id} + } + case providerspokenlanguage.EdgeLanguage: + if id := m.language; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *ProviderSpokenLanguageMutation) RemovedEdges() []string { + edges := make([]string, 0, 2) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *ProviderSpokenLanguageMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *ProviderSpokenLanguageMutation) ClearedEdges() []string { + edges := make([]string, 0, 2) + if m.clearedprofile { + edges = append(edges, providerspokenlanguage.EdgeProfile) + } + if m.clearedlanguage { + edges = append(edges, providerspokenlanguage.EdgeLanguage) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *ProviderSpokenLanguageMutation) EdgeCleared(name string) bool { + switch name { + case providerspokenlanguage.EdgeProfile: + return m.clearedprofile + case providerspokenlanguage.EdgeLanguage: + return m.clearedlanguage + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *ProviderSpokenLanguageMutation) ClearEdge(name string) error { + switch name { + case providerspokenlanguage.EdgeProfile: + m.ClearProfile() + return nil + case providerspokenlanguage.EdgeLanguage: + m.ClearLanguage() + return nil + } + return fmt.Errorf("unknown ProviderSpokenLanguage unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *ProviderSpokenLanguageMutation) ResetEdge(name string) error { + switch name { + case providerspokenlanguage.EdgeProfile: + m.ResetProfile() + return nil + case providerspokenlanguage.EdgeLanguage: + m.ResetLanguage() + return nil + } + return fmt.Errorf("unknown ProviderSpokenLanguage edge %s", name) +} + +// ServiceCategoryMutation represents an operation that mutates the ServiceCategory nodes in the graph. +type ServiceCategoryMutation struct { + config + op Op + typ string + id *uuid.UUID + slug *string + active *bool + sort_order *int + addsort_order *int + created_at *time.Time + updated_at *time.Time + clearedFields map[string]struct{} + children map[uuid.UUID]struct{} + removedchildren map[uuid.UUID]struct{} + clearedchildren bool + parent *uuid.UUID + clearedparent bool + localized_in map[string]struct{} + removedlocalized_in map[string]struct{} + clearedlocalized_in bool + done bool + oldValue func(context.Context) (*ServiceCategory, error) + predicates []predicate.ServiceCategory +} + +var _ ent.Mutation = (*ServiceCategoryMutation)(nil) + +// servicecategoryOption allows management of the mutation configuration using functional options. +type servicecategoryOption func(*ServiceCategoryMutation) + +// newServiceCategoryMutation creates new mutation for the ServiceCategory entity. +func newServiceCategoryMutation(c config, op Op, opts ...servicecategoryOption) *ServiceCategoryMutation { + m := &ServiceCategoryMutation{ + config: c, + op: op, + typ: TypeServiceCategory, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withServiceCategoryID sets the ID field of the mutation. +func withServiceCategoryID(id uuid.UUID) servicecategoryOption { + return func(m *ServiceCategoryMutation) { + var ( + err error + once sync.Once + value *ServiceCategory + ) + m.oldValue = func(ctx context.Context) (*ServiceCategory, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().ServiceCategory.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withServiceCategory sets the old ServiceCategory of the mutation. +func withServiceCategory(node *ServiceCategory) servicecategoryOption { + return func(m *ServiceCategoryMutation) { + m.oldValue = func(context.Context) (*ServiceCategory, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m ServiceCategoryMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m ServiceCategoryMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of ServiceCategory entities. +func (m *ServiceCategoryMutation) SetID(id uuid.UUID) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *ServiceCategoryMutation) ID() (id uuid.UUID, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *ServiceCategoryMutation) IDs(ctx context.Context) ([]uuid.UUID, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []uuid.UUID{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().ServiceCategory.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetParentID sets the "parent_id" field. +func (m *ServiceCategoryMutation) SetParentID(u uuid.UUID) { + m.parent = &u +} + +// ParentID returns the value of the "parent_id" field in the mutation. +func (m *ServiceCategoryMutation) ParentID() (r uuid.UUID, exists bool) { + v := m.parent + if v == nil { + return + } + return *v, true +} + +// OldParentID returns the old "parent_id" field's value of the ServiceCategory entity. +// If the ServiceCategory object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ServiceCategoryMutation) OldParentID(ctx context.Context) (v *uuid.UUID, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldParentID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldParentID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldParentID: %w", err) + } + return oldValue.ParentID, nil +} + +// ClearParentID clears the value of the "parent_id" field. +func (m *ServiceCategoryMutation) ClearParentID() { + m.parent = nil + m.clearedFields[servicecategory.FieldParentID] = struct{}{} +} + +// ParentIDCleared returns if the "parent_id" field was cleared in this mutation. +func (m *ServiceCategoryMutation) ParentIDCleared() bool { + _, ok := m.clearedFields[servicecategory.FieldParentID] + return ok +} + +// ResetParentID resets all changes to the "parent_id" field. +func (m *ServiceCategoryMutation) ResetParentID() { + m.parent = nil + delete(m.clearedFields, servicecategory.FieldParentID) +} + +// SetSlug sets the "slug" field. +func (m *ServiceCategoryMutation) SetSlug(s string) { + m.slug = &s +} + +// Slug returns the value of the "slug" field in the mutation. +func (m *ServiceCategoryMutation) Slug() (r string, exists bool) { + v := m.slug + if v == nil { + return + } + return *v, true +} + +// OldSlug returns the old "slug" field's value of the ServiceCategory entity. +// If the ServiceCategory object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ServiceCategoryMutation) OldSlug(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSlug is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSlug requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSlug: %w", err) + } + return oldValue.Slug, nil +} + +// ResetSlug resets all changes to the "slug" field. +func (m *ServiceCategoryMutation) ResetSlug() { + m.slug = nil +} + +// SetActive sets the "active" field. +func (m *ServiceCategoryMutation) SetActive(b bool) { + m.active = &b +} + +// Active returns the value of the "active" field in the mutation. +func (m *ServiceCategoryMutation) Active() (r bool, exists bool) { + v := m.active + if v == nil { + return + } + return *v, true +} + +// OldActive returns the old "active" field's value of the ServiceCategory entity. +// If the ServiceCategory object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ServiceCategoryMutation) OldActive(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldActive is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldActive requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldActive: %w", err) + } + return oldValue.Active, nil +} + +// ResetActive resets all changes to the "active" field. +func (m *ServiceCategoryMutation) ResetActive() { + m.active = nil +} + +// SetSortOrder sets the "sort_order" field. +func (m *ServiceCategoryMutation) SetSortOrder(i int) { + m.sort_order = &i + m.addsort_order = nil +} + +// SortOrder returns the value of the "sort_order" field in the mutation. +func (m *ServiceCategoryMutation) SortOrder() (r int, exists bool) { + v := m.sort_order + if v == nil { + return + } + return *v, true +} + +// OldSortOrder returns the old "sort_order" field's value of the ServiceCategory entity. +// If the ServiceCategory object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ServiceCategoryMutation) OldSortOrder(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSortOrder is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSortOrder requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSortOrder: %w", err) + } + return oldValue.SortOrder, nil +} + +// AddSortOrder adds i to the "sort_order" field. +func (m *ServiceCategoryMutation) AddSortOrder(i int) { + if m.addsort_order != nil { + *m.addsort_order += i + } else { + m.addsort_order = &i + } +} + +// AddedSortOrder returns the value that was added to the "sort_order" field in this mutation. +func (m *ServiceCategoryMutation) AddedSortOrder() (r int, exists bool) { + v := m.addsort_order + if v == nil { + return + } + return *v, true +} + +// ResetSortOrder resets all changes to the "sort_order" field. +func (m *ServiceCategoryMutation) ResetSortOrder() { + m.sort_order = nil + m.addsort_order = nil +} + +// SetCreatedAt sets the "created_at" field. +func (m *ServiceCategoryMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *ServiceCategoryMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the ServiceCategory entity. +// If the ServiceCategory object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ServiceCategoryMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *ServiceCategoryMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *ServiceCategoryMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *ServiceCategoryMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the ServiceCategory entity. +// If the ServiceCategory object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ServiceCategoryMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *ServiceCategoryMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// AddChildIDs adds the "children" edge to the ServiceCategory entity by ids. +func (m *ServiceCategoryMutation) AddChildIDs(ids ...uuid.UUID) { + if m.children == nil { + m.children = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.children[ids[i]] = struct{}{} + } +} + +// ClearChildren clears the "children" edge to the ServiceCategory entity. +func (m *ServiceCategoryMutation) ClearChildren() { + m.clearedchildren = true +} + +// ChildrenCleared reports if the "children" edge to the ServiceCategory entity was cleared. +func (m *ServiceCategoryMutation) ChildrenCleared() bool { + return m.clearedchildren +} + +// RemoveChildIDs removes the "children" edge to the ServiceCategory entity by IDs. +func (m *ServiceCategoryMutation) RemoveChildIDs(ids ...uuid.UUID) { + if m.removedchildren == nil { + m.removedchildren = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.children, ids[i]) + m.removedchildren[ids[i]] = struct{}{} + } +} + +// RemovedChildren returns the removed IDs of the "children" edge to the ServiceCategory entity. +func (m *ServiceCategoryMutation) RemovedChildrenIDs() (ids []uuid.UUID) { + for id := range m.removedchildren { + ids = append(ids, id) + } + return +} + +// ChildrenIDs returns the "children" edge IDs in the mutation. +func (m *ServiceCategoryMutation) ChildrenIDs() (ids []uuid.UUID) { + for id := range m.children { + ids = append(ids, id) + } + return +} + +// ResetChildren resets all changes to the "children" edge. +func (m *ServiceCategoryMutation) ResetChildren() { + m.children = nil + m.clearedchildren = false + m.removedchildren = nil +} + +// ClearParent clears the "parent" edge to the ServiceCategory entity. +func (m *ServiceCategoryMutation) ClearParent() { + m.clearedparent = true + m.clearedFields[servicecategory.FieldParentID] = struct{}{} +} + +// ParentCleared reports if the "parent" edge to the ServiceCategory entity was cleared. +func (m *ServiceCategoryMutation) ParentCleared() bool { + return m.ParentIDCleared() || m.clearedparent +} + +// ParentIDs returns the "parent" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// ParentID instead. It exists only for internal usage by the builders. +func (m *ServiceCategoryMutation) ParentIDs() (ids []uuid.UUID) { + if id := m.parent; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetParent resets all changes to the "parent" edge. +func (m *ServiceCategoryMutation) ResetParent() { + m.parent = nil + m.clearedparent = false +} + +// AddLocalizedInIDs adds the "localized_in" edge to the SupportedLocale entity by ids. +func (m *ServiceCategoryMutation) AddLocalizedInIDs(ids ...string) { + if m.localized_in == nil { + m.localized_in = make(map[string]struct{}) + } + for i := range ids { + m.localized_in[ids[i]] = struct{}{} + } +} + +// ClearLocalizedIn clears the "localized_in" edge to the SupportedLocale entity. +func (m *ServiceCategoryMutation) ClearLocalizedIn() { + m.clearedlocalized_in = true +} + +// LocalizedInCleared reports if the "localized_in" edge to the SupportedLocale entity was cleared. +func (m *ServiceCategoryMutation) LocalizedInCleared() bool { + return m.clearedlocalized_in +} + +// RemoveLocalizedInIDs removes the "localized_in" edge to the SupportedLocale entity by IDs. +func (m *ServiceCategoryMutation) RemoveLocalizedInIDs(ids ...string) { + if m.removedlocalized_in == nil { + m.removedlocalized_in = make(map[string]struct{}) + } + for i := range ids { + delete(m.localized_in, ids[i]) + m.removedlocalized_in[ids[i]] = struct{}{} + } +} + +// RemovedLocalizedIn returns the removed IDs of the "localized_in" edge to the SupportedLocale entity. +func (m *ServiceCategoryMutation) RemovedLocalizedInIDs() (ids []string) { + for id := range m.removedlocalized_in { + ids = append(ids, id) + } + return +} + +// LocalizedInIDs returns the "localized_in" edge IDs in the mutation. +func (m *ServiceCategoryMutation) LocalizedInIDs() (ids []string) { + for id := range m.localized_in { + ids = append(ids, id) + } + return +} + +// ResetLocalizedIn resets all changes to the "localized_in" edge. +func (m *ServiceCategoryMutation) ResetLocalizedIn() { + m.localized_in = nil + m.clearedlocalized_in = false + m.removedlocalized_in = nil +} + +// Where appends a list predicates to the ServiceCategoryMutation builder. +func (m *ServiceCategoryMutation) Where(ps ...predicate.ServiceCategory) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the ServiceCategoryMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *ServiceCategoryMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.ServiceCategory, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *ServiceCategoryMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *ServiceCategoryMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (ServiceCategory). +func (m *ServiceCategoryMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *ServiceCategoryMutation) Fields() []string { + fields := make([]string, 0, 6) + if m.parent != nil { + fields = append(fields, servicecategory.FieldParentID) + } + if m.slug != nil { + fields = append(fields, servicecategory.FieldSlug) + } + if m.active != nil { + fields = append(fields, servicecategory.FieldActive) + } + if m.sort_order != nil { + fields = append(fields, servicecategory.FieldSortOrder) + } + if m.created_at != nil { + fields = append(fields, servicecategory.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, servicecategory.FieldUpdatedAt) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *ServiceCategoryMutation) Field(name string) (ent.Value, bool) { + switch name { + case servicecategory.FieldParentID: + return m.ParentID() + case servicecategory.FieldSlug: + return m.Slug() + case servicecategory.FieldActive: + return m.Active() + case servicecategory.FieldSortOrder: + return m.SortOrder() + case servicecategory.FieldCreatedAt: + return m.CreatedAt() + case servicecategory.FieldUpdatedAt: + return m.UpdatedAt() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *ServiceCategoryMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case servicecategory.FieldParentID: + return m.OldParentID(ctx) + case servicecategory.FieldSlug: + return m.OldSlug(ctx) + case servicecategory.FieldActive: + return m.OldActive(ctx) + case servicecategory.FieldSortOrder: + return m.OldSortOrder(ctx) + case servicecategory.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case servicecategory.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + } + return nil, fmt.Errorf("unknown ServiceCategory field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ServiceCategoryMutation) SetField(name string, value ent.Value) error { + switch name { + case servicecategory.FieldParentID: + v, ok := value.(uuid.UUID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetParentID(v) + return nil + case servicecategory.FieldSlug: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSlug(v) + return nil + case servicecategory.FieldActive: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetActive(v) + return nil + case servicecategory.FieldSortOrder: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSortOrder(v) + return nil + case servicecategory.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case servicecategory.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown ServiceCategory field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *ServiceCategoryMutation) AddedFields() []string { + var fields []string + if m.addsort_order != nil { + fields = append(fields, servicecategory.FieldSortOrder) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *ServiceCategoryMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case servicecategory.FieldSortOrder: + return m.AddedSortOrder() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ServiceCategoryMutation) AddField(name string, value ent.Value) error { + switch name { + case servicecategory.FieldSortOrder: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddSortOrder(v) + return nil + } + return fmt.Errorf("unknown ServiceCategory numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *ServiceCategoryMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(servicecategory.FieldParentID) { + fields = append(fields, servicecategory.FieldParentID) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *ServiceCategoryMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *ServiceCategoryMutation) ClearField(name string) error { + switch name { + case servicecategory.FieldParentID: + m.ClearParentID() + return nil + } + return fmt.Errorf("unknown ServiceCategory nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *ServiceCategoryMutation) ResetField(name string) error { + switch name { + case servicecategory.FieldParentID: + m.ResetParentID() + return nil + case servicecategory.FieldSlug: + m.ResetSlug() + return nil + case servicecategory.FieldActive: + m.ResetActive() + return nil + case servicecategory.FieldSortOrder: + m.ResetSortOrder() + return nil + case servicecategory.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case servicecategory.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + } + return fmt.Errorf("unknown ServiceCategory field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *ServiceCategoryMutation) AddedEdges() []string { + edges := make([]string, 0, 3) + if m.children != nil { + edges = append(edges, servicecategory.EdgeChildren) + } + if m.parent != nil { + edges = append(edges, servicecategory.EdgeParent) + } + if m.localized_in != nil { + edges = append(edges, servicecategory.EdgeLocalizedIn) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *ServiceCategoryMutation) AddedIDs(name string) []ent.Value { + switch name { + case servicecategory.EdgeChildren: + ids := make([]ent.Value, 0, len(m.children)) + for id := range m.children { + ids = append(ids, id) + } + return ids + case servicecategory.EdgeParent: + if id := m.parent; id != nil { + return []ent.Value{*id} + } + case servicecategory.EdgeLocalizedIn: + ids := make([]ent.Value, 0, len(m.localized_in)) + for id := range m.localized_in { + ids = append(ids, id) + } + return ids + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *ServiceCategoryMutation) RemovedEdges() []string { + edges := make([]string, 0, 3) + if m.removedchildren != nil { + edges = append(edges, servicecategory.EdgeChildren) + } + if m.removedlocalized_in != nil { + edges = append(edges, servicecategory.EdgeLocalizedIn) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *ServiceCategoryMutation) RemovedIDs(name string) []ent.Value { + switch name { + case servicecategory.EdgeChildren: + ids := make([]ent.Value, 0, len(m.removedchildren)) + for id := range m.removedchildren { + ids = append(ids, id) + } + return ids + case servicecategory.EdgeLocalizedIn: + ids := make([]ent.Value, 0, len(m.removedlocalized_in)) + for id := range m.removedlocalized_in { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *ServiceCategoryMutation) ClearedEdges() []string { + edges := make([]string, 0, 3) + if m.clearedchildren { + edges = append(edges, servicecategory.EdgeChildren) + } + if m.clearedparent { + edges = append(edges, servicecategory.EdgeParent) + } + if m.clearedlocalized_in { + edges = append(edges, servicecategory.EdgeLocalizedIn) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *ServiceCategoryMutation) EdgeCleared(name string) bool { + switch name { + case servicecategory.EdgeChildren: + return m.clearedchildren + case servicecategory.EdgeParent: + return m.clearedparent + case servicecategory.EdgeLocalizedIn: + return m.clearedlocalized_in + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *ServiceCategoryMutation) ClearEdge(name string) error { + switch name { + case servicecategory.EdgeParent: + m.ClearParent() + return nil + } + return fmt.Errorf("unknown ServiceCategory unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *ServiceCategoryMutation) ResetEdge(name string) error { + switch name { + case servicecategory.EdgeChildren: + m.ResetChildren() + return nil + case servicecategory.EdgeParent: + m.ResetParent() + return nil + case servicecategory.EdgeLocalizedIn: + m.ResetLocalizedIn() + return nil + } + return fmt.Errorf("unknown ServiceCategory edge %s", name) +} + +// ServiceCategoryTranslationMutation represents an operation that mutates the ServiceCategoryTranslation nodes in the graph. +type ServiceCategoryTranslationMutation struct { + config + op Op + typ string + name *string + description *string + clearedFields map[string]struct{} + category *uuid.UUID + clearedcategory bool + locale_record *string + clearedlocale_record bool + done bool + oldValue func(context.Context) (*ServiceCategoryTranslation, error) + predicates []predicate.ServiceCategoryTranslation +} + +var _ ent.Mutation = (*ServiceCategoryTranslationMutation)(nil) + +// servicecategorytranslationOption allows management of the mutation configuration using functional options. +type servicecategorytranslationOption func(*ServiceCategoryTranslationMutation) + +// newServiceCategoryTranslationMutation creates new mutation for the ServiceCategoryTranslation entity. +func newServiceCategoryTranslationMutation(c config, op Op, opts ...servicecategorytranslationOption) *ServiceCategoryTranslationMutation { + m := &ServiceCategoryTranslationMutation{ + config: c, + op: op, + typ: TypeServiceCategoryTranslation, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m ServiceCategoryTranslationMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m ServiceCategoryTranslationMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetCategoryID sets the "category_id" field. +func (m *ServiceCategoryTranslationMutation) SetCategoryID(u uuid.UUID) { + m.category = &u +} + +// CategoryID returns the value of the "category_id" field in the mutation. +func (m *ServiceCategoryTranslationMutation) CategoryID() (r uuid.UUID, exists bool) { + v := m.category + if v == nil { + return + } + return *v, true +} + +// ResetCategoryID resets all changes to the "category_id" field. +func (m *ServiceCategoryTranslationMutation) ResetCategoryID() { + m.category = nil +} + +// SetLocale sets the "locale" field. +func (m *ServiceCategoryTranslationMutation) SetLocale(s string) { + m.locale_record = &s +} + +// Locale returns the value of the "locale" field in the mutation. +func (m *ServiceCategoryTranslationMutation) Locale() (r string, exists bool) { + v := m.locale_record + if v == nil { + return + } + return *v, true +} + +// ResetLocale resets all changes to the "locale" field. +func (m *ServiceCategoryTranslationMutation) ResetLocale() { + m.locale_record = nil +} + +// SetName sets the "name" field. +func (m *ServiceCategoryTranslationMutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *ServiceCategoryTranslationMutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return + } + return *v, true +} + +// ResetName resets all changes to the "name" field. +func (m *ServiceCategoryTranslationMutation) ResetName() { + m.name = nil +} + +// SetDescription sets the "description" field. +func (m *ServiceCategoryTranslationMutation) SetDescription(s string) { + m.description = &s +} + +// Description returns the value of the "description" field in the mutation. +func (m *ServiceCategoryTranslationMutation) Description() (r string, exists bool) { + v := m.description + if v == nil { + return + } + return *v, true +} + +// ClearDescription clears the value of the "description" field. +func (m *ServiceCategoryTranslationMutation) ClearDescription() { + m.description = nil + m.clearedFields[servicecategorytranslation.FieldDescription] = struct{}{} +} + +// DescriptionCleared returns if the "description" field was cleared in this mutation. +func (m *ServiceCategoryTranslationMutation) DescriptionCleared() bool { + _, ok := m.clearedFields[servicecategorytranslation.FieldDescription] + return ok +} + +// ResetDescription resets all changes to the "description" field. +func (m *ServiceCategoryTranslationMutation) ResetDescription() { + m.description = nil + delete(m.clearedFields, servicecategorytranslation.FieldDescription) +} + +// ClearCategory clears the "category" edge to the ServiceCategory entity. +func (m *ServiceCategoryTranslationMutation) ClearCategory() { + m.clearedcategory = true + m.clearedFields[servicecategorytranslation.FieldCategoryID] = struct{}{} +} + +// CategoryCleared reports if the "category" edge to the ServiceCategory entity was cleared. +func (m *ServiceCategoryTranslationMutation) CategoryCleared() bool { + return m.clearedcategory +} + +// CategoryIDs returns the "category" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// CategoryID instead. It exists only for internal usage by the builders. +func (m *ServiceCategoryTranslationMutation) CategoryIDs() (ids []uuid.UUID) { + if id := m.category; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetCategory resets all changes to the "category" edge. +func (m *ServiceCategoryTranslationMutation) ResetCategory() { + m.category = nil + m.clearedcategory = false +} + +// SetLocaleRecordID sets the "locale_record" edge to the SupportedLocale entity by id. +func (m *ServiceCategoryTranslationMutation) SetLocaleRecordID(id string) { + m.locale_record = &id +} + +// ClearLocaleRecord clears the "locale_record" edge to the SupportedLocale entity. +func (m *ServiceCategoryTranslationMutation) ClearLocaleRecord() { + m.clearedlocale_record = true + m.clearedFields[servicecategorytranslation.FieldLocale] = struct{}{} +} + +// LocaleRecordCleared reports if the "locale_record" edge to the SupportedLocale entity was cleared. +func (m *ServiceCategoryTranslationMutation) LocaleRecordCleared() bool { + return m.clearedlocale_record +} + +// LocaleRecordID returns the "locale_record" edge ID in the mutation. +func (m *ServiceCategoryTranslationMutation) LocaleRecordID() (id string, exists bool) { + if m.locale_record != nil { + return *m.locale_record, true + } + return +} + +// LocaleRecordIDs returns the "locale_record" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// LocaleRecordID instead. It exists only for internal usage by the builders. +func (m *ServiceCategoryTranslationMutation) LocaleRecordIDs() (ids []string) { + if id := m.locale_record; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetLocaleRecord resets all changes to the "locale_record" edge. +func (m *ServiceCategoryTranslationMutation) ResetLocaleRecord() { + m.locale_record = nil + m.clearedlocale_record = false +} + +// Where appends a list predicates to the ServiceCategoryTranslationMutation builder. +func (m *ServiceCategoryTranslationMutation) Where(ps ...predicate.ServiceCategoryTranslation) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the ServiceCategoryTranslationMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *ServiceCategoryTranslationMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.ServiceCategoryTranslation, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *ServiceCategoryTranslationMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *ServiceCategoryTranslationMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (ServiceCategoryTranslation). +func (m *ServiceCategoryTranslationMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *ServiceCategoryTranslationMutation) Fields() []string { + fields := make([]string, 0, 4) + if m.category != nil { + fields = append(fields, servicecategorytranslation.FieldCategoryID) + } + if m.locale_record != nil { + fields = append(fields, servicecategorytranslation.FieldLocale) + } + if m.name != nil { + fields = append(fields, servicecategorytranslation.FieldName) + } + if m.description != nil { + fields = append(fields, servicecategorytranslation.FieldDescription) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *ServiceCategoryTranslationMutation) Field(name string) (ent.Value, bool) { + switch name { + case servicecategorytranslation.FieldCategoryID: + return m.CategoryID() + case servicecategorytranslation.FieldLocale: + return m.Locale() + case servicecategorytranslation.FieldName: + return m.Name() + case servicecategorytranslation.FieldDescription: + return m.Description() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *ServiceCategoryTranslationMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + return nil, errors.New("edge schema ServiceCategoryTranslation does not support getting old values") +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ServiceCategoryTranslationMutation) SetField(name string, value ent.Value) error { + switch name { + case servicecategorytranslation.FieldCategoryID: + v, ok := value.(uuid.UUID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCategoryID(v) + return nil + case servicecategorytranslation.FieldLocale: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLocale(v) + return nil + case servicecategorytranslation.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + case servicecategorytranslation.FieldDescription: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetDescription(v) + return nil + } + return fmt.Errorf("unknown ServiceCategoryTranslation field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *ServiceCategoryTranslationMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *ServiceCategoryTranslationMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ServiceCategoryTranslationMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown ServiceCategoryTranslation numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *ServiceCategoryTranslationMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(servicecategorytranslation.FieldDescription) { + fields = append(fields, servicecategorytranslation.FieldDescription) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *ServiceCategoryTranslationMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *ServiceCategoryTranslationMutation) ClearField(name string) error { + switch name { + case servicecategorytranslation.FieldDescription: + m.ClearDescription() + return nil + } + return fmt.Errorf("unknown ServiceCategoryTranslation nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *ServiceCategoryTranslationMutation) ResetField(name string) error { + switch name { + case servicecategorytranslation.FieldCategoryID: + m.ResetCategoryID() + return nil + case servicecategorytranslation.FieldLocale: + m.ResetLocale() + return nil + case servicecategorytranslation.FieldName: + m.ResetName() + return nil + case servicecategorytranslation.FieldDescription: + m.ResetDescription() + return nil + } + return fmt.Errorf("unknown ServiceCategoryTranslation field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *ServiceCategoryTranslationMutation) AddedEdges() []string { + edges := make([]string, 0, 2) + if m.category != nil { + edges = append(edges, servicecategorytranslation.EdgeCategory) + } + if m.locale_record != nil { + edges = append(edges, servicecategorytranslation.EdgeLocaleRecord) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *ServiceCategoryTranslationMutation) AddedIDs(name string) []ent.Value { + switch name { + case servicecategorytranslation.EdgeCategory: + if id := m.category; id != nil { + return []ent.Value{*id} + } + case servicecategorytranslation.EdgeLocaleRecord: + if id := m.locale_record; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *ServiceCategoryTranslationMutation) RemovedEdges() []string { + edges := make([]string, 0, 2) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *ServiceCategoryTranslationMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *ServiceCategoryTranslationMutation) ClearedEdges() []string { + edges := make([]string, 0, 2) + if m.clearedcategory { + edges = append(edges, servicecategorytranslation.EdgeCategory) + } + if m.clearedlocale_record { + edges = append(edges, servicecategorytranslation.EdgeLocaleRecord) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *ServiceCategoryTranslationMutation) EdgeCleared(name string) bool { + switch name { + case servicecategorytranslation.EdgeCategory: + return m.clearedcategory + case servicecategorytranslation.EdgeLocaleRecord: + return m.clearedlocale_record + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *ServiceCategoryTranslationMutation) ClearEdge(name string) error { + switch name { + case servicecategorytranslation.EdgeCategory: + m.ClearCategory() + return nil + case servicecategorytranslation.EdgeLocaleRecord: + m.ClearLocaleRecord() + return nil + } + return fmt.Errorf("unknown ServiceCategoryTranslation unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *ServiceCategoryTranslationMutation) ResetEdge(name string) error { + switch name { + case servicecategorytranslation.EdgeCategory: + m.ResetCategory() + return nil + case servicecategorytranslation.EdgeLocaleRecord: + m.ResetLocaleRecord() + return nil + } + return fmt.Errorf("unknown ServiceCategoryTranslation edge %s", name) +} + +// SpokenLanguageMutation represents an operation that mutates the SpokenLanguage nodes in the graph. +type SpokenLanguageMutation struct { + config + op Op + typ string + id *string + active *bool + sort_order *int + addsort_order *int + clearedFields map[string]struct{} + localized_in map[string]struct{} + removedlocalized_in map[string]struct{} + clearedlocalized_in bool + provider_profiles map[uuid.UUID]struct{} + removedprovider_profiles map[uuid.UUID]struct{} + clearedprovider_profiles bool + done bool + oldValue func(context.Context) (*SpokenLanguage, error) + predicates []predicate.SpokenLanguage +} + +var _ ent.Mutation = (*SpokenLanguageMutation)(nil) + +// spokenlanguageOption allows management of the mutation configuration using functional options. +type spokenlanguageOption func(*SpokenLanguageMutation) + +// newSpokenLanguageMutation creates new mutation for the SpokenLanguage entity. +func newSpokenLanguageMutation(c config, op Op, opts ...spokenlanguageOption) *SpokenLanguageMutation { + m := &SpokenLanguageMutation{ + config: c, + op: op, + typ: TypeSpokenLanguage, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withSpokenLanguageID sets the ID field of the mutation. +func withSpokenLanguageID(id string) spokenlanguageOption { + return func(m *SpokenLanguageMutation) { + var ( + err error + once sync.Once + value *SpokenLanguage + ) + m.oldValue = func(ctx context.Context) (*SpokenLanguage, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().SpokenLanguage.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withSpokenLanguage sets the old SpokenLanguage of the mutation. +func withSpokenLanguage(node *SpokenLanguage) spokenlanguageOption { + return func(m *SpokenLanguageMutation) { + m.oldValue = func(context.Context) (*SpokenLanguage, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m SpokenLanguageMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m SpokenLanguageMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of SpokenLanguage entities. +func (m *SpokenLanguageMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *SpokenLanguageMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *SpokenLanguageMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().SpokenLanguage.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetActive sets the "active" field. +func (m *SpokenLanguageMutation) SetActive(b bool) { + m.active = &b +} + +// Active returns the value of the "active" field in the mutation. +func (m *SpokenLanguageMutation) Active() (r bool, exists bool) { + v := m.active + if v == nil { + return + } + return *v, true +} + +// OldActive returns the old "active" field's value of the SpokenLanguage entity. +// If the SpokenLanguage object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SpokenLanguageMutation) OldActive(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldActive is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldActive requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldActive: %w", err) + } + return oldValue.Active, nil +} + +// ResetActive resets all changes to the "active" field. +func (m *SpokenLanguageMutation) ResetActive() { + m.active = nil +} + +// SetSortOrder sets the "sort_order" field. +func (m *SpokenLanguageMutation) SetSortOrder(i int) { + m.sort_order = &i + m.addsort_order = nil +} + +// SortOrder returns the value of the "sort_order" field in the mutation. +func (m *SpokenLanguageMutation) SortOrder() (r int, exists bool) { + v := m.sort_order + if v == nil { + return + } + return *v, true +} + +// OldSortOrder returns the old "sort_order" field's value of the SpokenLanguage entity. +// If the SpokenLanguage object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SpokenLanguageMutation) OldSortOrder(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSortOrder is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSortOrder requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSortOrder: %w", err) + } + return oldValue.SortOrder, nil +} + +// AddSortOrder adds i to the "sort_order" field. +func (m *SpokenLanguageMutation) AddSortOrder(i int) { + if m.addsort_order != nil { + *m.addsort_order += i + } else { + m.addsort_order = &i + } +} + +// AddedSortOrder returns the value that was added to the "sort_order" field in this mutation. +func (m *SpokenLanguageMutation) AddedSortOrder() (r int, exists bool) { + v := m.addsort_order + if v == nil { + return + } + return *v, true +} + +// ResetSortOrder resets all changes to the "sort_order" field. +func (m *SpokenLanguageMutation) ResetSortOrder() { + m.sort_order = nil + m.addsort_order = nil +} + +// AddLocalizedInIDs adds the "localized_in" edge to the SupportedLocale entity by ids. +func (m *SpokenLanguageMutation) AddLocalizedInIDs(ids ...string) { + if m.localized_in == nil { + m.localized_in = make(map[string]struct{}) + } + for i := range ids { + m.localized_in[ids[i]] = struct{}{} + } +} + +// ClearLocalizedIn clears the "localized_in" edge to the SupportedLocale entity. +func (m *SpokenLanguageMutation) ClearLocalizedIn() { + m.clearedlocalized_in = true +} + +// LocalizedInCleared reports if the "localized_in" edge to the SupportedLocale entity was cleared. +func (m *SpokenLanguageMutation) LocalizedInCleared() bool { + return m.clearedlocalized_in +} + +// RemoveLocalizedInIDs removes the "localized_in" edge to the SupportedLocale entity by IDs. +func (m *SpokenLanguageMutation) RemoveLocalizedInIDs(ids ...string) { + if m.removedlocalized_in == nil { + m.removedlocalized_in = make(map[string]struct{}) + } + for i := range ids { + delete(m.localized_in, ids[i]) + m.removedlocalized_in[ids[i]] = struct{}{} + } +} + +// RemovedLocalizedIn returns the removed IDs of the "localized_in" edge to the SupportedLocale entity. +func (m *SpokenLanguageMutation) RemovedLocalizedInIDs() (ids []string) { + for id := range m.removedlocalized_in { + ids = append(ids, id) + } + return +} + +// LocalizedInIDs returns the "localized_in" edge IDs in the mutation. +func (m *SpokenLanguageMutation) LocalizedInIDs() (ids []string) { + for id := range m.localized_in { + ids = append(ids, id) + } + return +} + +// ResetLocalizedIn resets all changes to the "localized_in" edge. +func (m *SpokenLanguageMutation) ResetLocalizedIn() { + m.localized_in = nil + m.clearedlocalized_in = false + m.removedlocalized_in = nil +} + +// AddProviderProfileIDs adds the "provider_profiles" edge to the ProviderProfile entity by ids. +func (m *SpokenLanguageMutation) AddProviderProfileIDs(ids ...uuid.UUID) { + if m.provider_profiles == nil { + m.provider_profiles = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.provider_profiles[ids[i]] = struct{}{} + } +} + +// ClearProviderProfiles clears the "provider_profiles" edge to the ProviderProfile entity. +func (m *SpokenLanguageMutation) ClearProviderProfiles() { + m.clearedprovider_profiles = true +} + +// ProviderProfilesCleared reports if the "provider_profiles" edge to the ProviderProfile entity was cleared. +func (m *SpokenLanguageMutation) ProviderProfilesCleared() bool { + return m.clearedprovider_profiles +} + +// RemoveProviderProfileIDs removes the "provider_profiles" edge to the ProviderProfile entity by IDs. +func (m *SpokenLanguageMutation) RemoveProviderProfileIDs(ids ...uuid.UUID) { + if m.removedprovider_profiles == nil { + m.removedprovider_profiles = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.provider_profiles, ids[i]) + m.removedprovider_profiles[ids[i]] = struct{}{} + } +} + +// RemovedProviderProfiles returns the removed IDs of the "provider_profiles" edge to the ProviderProfile entity. +func (m *SpokenLanguageMutation) RemovedProviderProfilesIDs() (ids []uuid.UUID) { + for id := range m.removedprovider_profiles { + ids = append(ids, id) + } + return +} + +// ProviderProfilesIDs returns the "provider_profiles" edge IDs in the mutation. +func (m *SpokenLanguageMutation) ProviderProfilesIDs() (ids []uuid.UUID) { + for id := range m.provider_profiles { + ids = append(ids, id) + } + return +} + +// ResetProviderProfiles resets all changes to the "provider_profiles" edge. +func (m *SpokenLanguageMutation) ResetProviderProfiles() { + m.provider_profiles = nil + m.clearedprovider_profiles = false + m.removedprovider_profiles = nil +} + +// Where appends a list predicates to the SpokenLanguageMutation builder. +func (m *SpokenLanguageMutation) Where(ps ...predicate.SpokenLanguage) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the SpokenLanguageMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *SpokenLanguageMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.SpokenLanguage, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *SpokenLanguageMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *SpokenLanguageMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (SpokenLanguage). +func (m *SpokenLanguageMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *SpokenLanguageMutation) Fields() []string { + fields := make([]string, 0, 2) + if m.active != nil { + fields = append(fields, spokenlanguage.FieldActive) + } + if m.sort_order != nil { + fields = append(fields, spokenlanguage.FieldSortOrder) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *SpokenLanguageMutation) Field(name string) (ent.Value, bool) { + switch name { + case spokenlanguage.FieldActive: + return m.Active() + case spokenlanguage.FieldSortOrder: + return m.SortOrder() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *SpokenLanguageMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case spokenlanguage.FieldActive: + return m.OldActive(ctx) + case spokenlanguage.FieldSortOrder: + return m.OldSortOrder(ctx) + } + return nil, fmt.Errorf("unknown SpokenLanguage field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *SpokenLanguageMutation) SetField(name string, value ent.Value) error { + switch name { + case spokenlanguage.FieldActive: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetActive(v) + return nil + case spokenlanguage.FieldSortOrder: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSortOrder(v) + return nil + } + return fmt.Errorf("unknown SpokenLanguage field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *SpokenLanguageMutation) AddedFields() []string { + var fields []string + if m.addsort_order != nil { + fields = append(fields, spokenlanguage.FieldSortOrder) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *SpokenLanguageMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case spokenlanguage.FieldSortOrder: + return m.AddedSortOrder() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *SpokenLanguageMutation) AddField(name string, value ent.Value) error { + switch name { + case spokenlanguage.FieldSortOrder: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddSortOrder(v) + return nil + } + return fmt.Errorf("unknown SpokenLanguage numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *SpokenLanguageMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *SpokenLanguageMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *SpokenLanguageMutation) ClearField(name string) error { + return fmt.Errorf("unknown SpokenLanguage nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *SpokenLanguageMutation) ResetField(name string) error { + switch name { + case spokenlanguage.FieldActive: + m.ResetActive() + return nil + case spokenlanguage.FieldSortOrder: + m.ResetSortOrder() + return nil + } + return fmt.Errorf("unknown SpokenLanguage field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *SpokenLanguageMutation) AddedEdges() []string { + edges := make([]string, 0, 2) + if m.localized_in != nil { + edges = append(edges, spokenlanguage.EdgeLocalizedIn) + } + if m.provider_profiles != nil { + edges = append(edges, spokenlanguage.EdgeProviderProfiles) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *SpokenLanguageMutation) AddedIDs(name string) []ent.Value { + switch name { + case spokenlanguage.EdgeLocalizedIn: + ids := make([]ent.Value, 0, len(m.localized_in)) + for id := range m.localized_in { + ids = append(ids, id) + } + return ids + case spokenlanguage.EdgeProviderProfiles: + ids := make([]ent.Value, 0, len(m.provider_profiles)) + for id := range m.provider_profiles { + ids = append(ids, id) + } + return ids + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *SpokenLanguageMutation) RemovedEdges() []string { + edges := make([]string, 0, 2) + if m.removedlocalized_in != nil { + edges = append(edges, spokenlanguage.EdgeLocalizedIn) + } + if m.removedprovider_profiles != nil { + edges = append(edges, spokenlanguage.EdgeProviderProfiles) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *SpokenLanguageMutation) RemovedIDs(name string) []ent.Value { + switch name { + case spokenlanguage.EdgeLocalizedIn: + ids := make([]ent.Value, 0, len(m.removedlocalized_in)) + for id := range m.removedlocalized_in { + ids = append(ids, id) + } + return ids + case spokenlanguage.EdgeProviderProfiles: + ids := make([]ent.Value, 0, len(m.removedprovider_profiles)) + for id := range m.removedprovider_profiles { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *SpokenLanguageMutation) ClearedEdges() []string { + edges := make([]string, 0, 2) + if m.clearedlocalized_in { + edges = append(edges, spokenlanguage.EdgeLocalizedIn) + } + if m.clearedprovider_profiles { + edges = append(edges, spokenlanguage.EdgeProviderProfiles) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *SpokenLanguageMutation) EdgeCleared(name string) bool { + switch name { + case spokenlanguage.EdgeLocalizedIn: + return m.clearedlocalized_in + case spokenlanguage.EdgeProviderProfiles: + return m.clearedprovider_profiles + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *SpokenLanguageMutation) ClearEdge(name string) error { + switch name { + } + return fmt.Errorf("unknown SpokenLanguage unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *SpokenLanguageMutation) ResetEdge(name string) error { + switch name { + case spokenlanguage.EdgeLocalizedIn: + m.ResetLocalizedIn() + return nil + case spokenlanguage.EdgeProviderProfiles: + m.ResetProviderProfiles() + return nil + } + return fmt.Errorf("unknown SpokenLanguage edge %s", name) +} + +// SpokenLanguageTranslationMutation represents an operation that mutates the SpokenLanguageTranslation nodes in the graph. +type SpokenLanguageTranslationMutation struct { + config + op Op + typ string + name *string + clearedFields map[string]struct{} + language *string + clearedlanguage bool + locale_record *string + clearedlocale_record bool + done bool + oldValue func(context.Context) (*SpokenLanguageTranslation, error) + predicates []predicate.SpokenLanguageTranslation +} + +var _ ent.Mutation = (*SpokenLanguageTranslationMutation)(nil) + +// spokenlanguagetranslationOption allows management of the mutation configuration using functional options. +type spokenlanguagetranslationOption func(*SpokenLanguageTranslationMutation) + +// newSpokenLanguageTranslationMutation creates new mutation for the SpokenLanguageTranslation entity. +func newSpokenLanguageTranslationMutation(c config, op Op, opts ...spokenlanguagetranslationOption) *SpokenLanguageTranslationMutation { + m := &SpokenLanguageTranslationMutation{ + config: c, + op: op, + typ: TypeSpokenLanguageTranslation, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m SpokenLanguageTranslationMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m SpokenLanguageTranslationMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetLanguageCode sets the "language_code" field. +func (m *SpokenLanguageTranslationMutation) SetLanguageCode(s string) { + m.language = &s +} + +// LanguageCode returns the value of the "language_code" field in the mutation. +func (m *SpokenLanguageTranslationMutation) LanguageCode() (r string, exists bool) { + v := m.language + if v == nil { + return + } + return *v, true +} + +// ResetLanguageCode resets all changes to the "language_code" field. +func (m *SpokenLanguageTranslationMutation) ResetLanguageCode() { + m.language = nil +} + +// SetLocale sets the "locale" field. +func (m *SpokenLanguageTranslationMutation) SetLocale(s string) { + m.locale_record = &s +} + +// Locale returns the value of the "locale" field in the mutation. +func (m *SpokenLanguageTranslationMutation) Locale() (r string, exists bool) { + v := m.locale_record + if v == nil { + return + } + return *v, true +} + +// ResetLocale resets all changes to the "locale" field. +func (m *SpokenLanguageTranslationMutation) ResetLocale() { + m.locale_record = nil +} + +// SetName sets the "name" field. +func (m *SpokenLanguageTranslationMutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *SpokenLanguageTranslationMutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return + } + return *v, true +} + +// ResetName resets all changes to the "name" field. +func (m *SpokenLanguageTranslationMutation) ResetName() { + m.name = nil +} + +// SetLanguageID sets the "language" edge to the SpokenLanguage entity by id. +func (m *SpokenLanguageTranslationMutation) SetLanguageID(id string) { + m.language = &id +} + +// ClearLanguage clears the "language" edge to the SpokenLanguage entity. +func (m *SpokenLanguageTranslationMutation) ClearLanguage() { + m.clearedlanguage = true + m.clearedFields[spokenlanguagetranslation.FieldLanguageCode] = struct{}{} +} + +// LanguageCleared reports if the "language" edge to the SpokenLanguage entity was cleared. +func (m *SpokenLanguageTranslationMutation) LanguageCleared() bool { + return m.clearedlanguage +} + +// LanguageID returns the "language" edge ID in the mutation. +func (m *SpokenLanguageTranslationMutation) LanguageID() (id string, exists bool) { + if m.language != nil { + return *m.language, true + } + return +} + +// LanguageIDs returns the "language" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// LanguageID instead. It exists only for internal usage by the builders. +func (m *SpokenLanguageTranslationMutation) LanguageIDs() (ids []string) { + if id := m.language; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetLanguage resets all changes to the "language" edge. +func (m *SpokenLanguageTranslationMutation) ResetLanguage() { + m.language = nil + m.clearedlanguage = false +} + +// SetLocaleRecordID sets the "locale_record" edge to the SupportedLocale entity by id. +func (m *SpokenLanguageTranslationMutation) SetLocaleRecordID(id string) { + m.locale_record = &id +} + +// ClearLocaleRecord clears the "locale_record" edge to the SupportedLocale entity. +func (m *SpokenLanguageTranslationMutation) ClearLocaleRecord() { + m.clearedlocale_record = true + m.clearedFields[spokenlanguagetranslation.FieldLocale] = struct{}{} +} + +// LocaleRecordCleared reports if the "locale_record" edge to the SupportedLocale entity was cleared. +func (m *SpokenLanguageTranslationMutation) LocaleRecordCleared() bool { + return m.clearedlocale_record +} + +// LocaleRecordID returns the "locale_record" edge ID in the mutation. +func (m *SpokenLanguageTranslationMutation) LocaleRecordID() (id string, exists bool) { + if m.locale_record != nil { + return *m.locale_record, true + } + return +} + +// LocaleRecordIDs returns the "locale_record" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// LocaleRecordID instead. It exists only for internal usage by the builders. +func (m *SpokenLanguageTranslationMutation) LocaleRecordIDs() (ids []string) { + if id := m.locale_record; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetLocaleRecord resets all changes to the "locale_record" edge. +func (m *SpokenLanguageTranslationMutation) ResetLocaleRecord() { + m.locale_record = nil + m.clearedlocale_record = false +} + +// Where appends a list predicates to the SpokenLanguageTranslationMutation builder. +func (m *SpokenLanguageTranslationMutation) Where(ps ...predicate.SpokenLanguageTranslation) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the SpokenLanguageTranslationMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *SpokenLanguageTranslationMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.SpokenLanguageTranslation, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *SpokenLanguageTranslationMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *SpokenLanguageTranslationMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (SpokenLanguageTranslation). +func (m *SpokenLanguageTranslationMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *SpokenLanguageTranslationMutation) Fields() []string { + fields := make([]string, 0, 3) + if m.language != nil { + fields = append(fields, spokenlanguagetranslation.FieldLanguageCode) + } + if m.locale_record != nil { + fields = append(fields, spokenlanguagetranslation.FieldLocale) + } + if m.name != nil { + fields = append(fields, spokenlanguagetranslation.FieldName) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *SpokenLanguageTranslationMutation) Field(name string) (ent.Value, bool) { + switch name { + case spokenlanguagetranslation.FieldLanguageCode: + return m.LanguageCode() + case spokenlanguagetranslation.FieldLocale: + return m.Locale() + case spokenlanguagetranslation.FieldName: + return m.Name() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *SpokenLanguageTranslationMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + return nil, errors.New("edge schema SpokenLanguageTranslation does not support getting old values") +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *SpokenLanguageTranslationMutation) SetField(name string, value ent.Value) error { + switch name { + case spokenlanguagetranslation.FieldLanguageCode: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLanguageCode(v) + return nil + case spokenlanguagetranslation.FieldLocale: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLocale(v) + return nil + case spokenlanguagetranslation.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + } + return fmt.Errorf("unknown SpokenLanguageTranslation field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *SpokenLanguageTranslationMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *SpokenLanguageTranslationMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *SpokenLanguageTranslationMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown SpokenLanguageTranslation numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *SpokenLanguageTranslationMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *SpokenLanguageTranslationMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *SpokenLanguageTranslationMutation) ClearField(name string) error { + return fmt.Errorf("unknown SpokenLanguageTranslation nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *SpokenLanguageTranslationMutation) ResetField(name string) error { + switch name { + case spokenlanguagetranslation.FieldLanguageCode: + m.ResetLanguageCode() + return nil + case spokenlanguagetranslation.FieldLocale: + m.ResetLocale() + return nil + case spokenlanguagetranslation.FieldName: + m.ResetName() + return nil + } + return fmt.Errorf("unknown SpokenLanguageTranslation field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *SpokenLanguageTranslationMutation) AddedEdges() []string { + edges := make([]string, 0, 2) + if m.language != nil { + edges = append(edges, spokenlanguagetranslation.EdgeLanguage) + } + if m.locale_record != nil { + edges = append(edges, spokenlanguagetranslation.EdgeLocaleRecord) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *SpokenLanguageTranslationMutation) AddedIDs(name string) []ent.Value { + switch name { + case spokenlanguagetranslation.EdgeLanguage: + if id := m.language; id != nil { + return []ent.Value{*id} + } + case spokenlanguagetranslation.EdgeLocaleRecord: + if id := m.locale_record; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *SpokenLanguageTranslationMutation) RemovedEdges() []string { + edges := make([]string, 0, 2) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *SpokenLanguageTranslationMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *SpokenLanguageTranslationMutation) ClearedEdges() []string { + edges := make([]string, 0, 2) + if m.clearedlanguage { + edges = append(edges, spokenlanguagetranslation.EdgeLanguage) + } + if m.clearedlocale_record { + edges = append(edges, spokenlanguagetranslation.EdgeLocaleRecord) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *SpokenLanguageTranslationMutation) EdgeCleared(name string) bool { + switch name { + case spokenlanguagetranslation.EdgeLanguage: + return m.clearedlanguage + case spokenlanguagetranslation.EdgeLocaleRecord: + return m.clearedlocale_record + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *SpokenLanguageTranslationMutation) ClearEdge(name string) error { + switch name { + case spokenlanguagetranslation.EdgeLanguage: + m.ClearLanguage() + return nil + case spokenlanguagetranslation.EdgeLocaleRecord: + m.ClearLocaleRecord() + return nil + } + return fmt.Errorf("unknown SpokenLanguageTranslation unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *SpokenLanguageTranslationMutation) ResetEdge(name string) error { + switch name { + case spokenlanguagetranslation.EdgeLanguage: + m.ResetLanguage() + return nil + case spokenlanguagetranslation.EdgeLocaleRecord: + m.ResetLocaleRecord() + return nil + } + return fmt.Errorf("unknown SpokenLanguageTranslation edge %s", name) +} + +// SupportedLocaleMutation represents an operation that mutates the SupportedLocale nodes in the graph. +type SupportedLocaleMutation struct { + config + op Op + typ string + id *string + active *bool + sort_order *int + addsort_order *int + clearedFields map[string]struct{} + translated_categories map[uuid.UUID]struct{} + removedtranslated_categories map[uuid.UUID]struct{} + clearedtranslated_categories bool + translated_languages map[string]struct{} + removedtranslated_languages map[string]struct{} + clearedtranslated_languages bool + done bool + oldValue func(context.Context) (*SupportedLocale, error) + predicates []predicate.SupportedLocale } -var _ ent.Mutation = (*InternalUserMutation)(nil) +var _ ent.Mutation = (*SupportedLocaleMutation)(nil) -// internaluserOption allows management of the mutation configuration using functional options. -type internaluserOption func(*InternalUserMutation) +// supportedlocaleOption allows management of the mutation configuration using functional options. +type supportedlocaleOption func(*SupportedLocaleMutation) -// newInternalUserMutation creates new mutation for the InternalUser entity. -func newInternalUserMutation(c config, op Op, opts ...internaluserOption) *InternalUserMutation { - m := &InternalUserMutation{ +// newSupportedLocaleMutation creates new mutation for the SupportedLocale entity. +func newSupportedLocaleMutation(c config, op Op, opts ...supportedlocaleOption) *SupportedLocaleMutation { + m := &SupportedLocaleMutation{ config: c, op: op, - typ: TypeInternalUser, + typ: TypeSupportedLocale, clearedFields: make(map[string]struct{}), } for _, opt := range opts { @@ -64,20 +6903,20 @@ func newInternalUserMutation(c config, op Op, opts ...internaluserOption) *Inter return m } -// withInternalUserID sets the ID field of the mutation. -func withInternalUserID(id uuid.UUID) internaluserOption { - return func(m *InternalUserMutation) { +// withSupportedLocaleID sets the ID field of the mutation. +func withSupportedLocaleID(id string) supportedlocaleOption { + return func(m *SupportedLocaleMutation) { var ( err error once sync.Once - value *InternalUser + value *SupportedLocale ) - m.oldValue = func(ctx context.Context) (*InternalUser, error) { + m.oldValue = func(ctx context.Context) (*SupportedLocale, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { - value, err = m.Client().InternalUser.Get(ctx, id) + value, err = m.Client().SupportedLocale.Get(ctx, id) } }) return value, err @@ -86,10 +6925,10 @@ func withInternalUserID(id uuid.UUID) internaluserOption { } } -// withInternalUser sets the old InternalUser of the mutation. -func withInternalUser(node *InternalUser) internaluserOption { - return func(m *InternalUserMutation) { - m.oldValue = func(context.Context) (*InternalUser, error) { +// withSupportedLocale sets the old SupportedLocale of the mutation. +func withSupportedLocale(node *SupportedLocale) supportedlocaleOption { + return func(m *SupportedLocaleMutation) { + m.oldValue = func(context.Context) (*SupportedLocale, error) { return node, nil } m.id = &node.ID @@ -98,7 +6937,7 @@ func withInternalUser(node *InternalUser) internaluserOption { // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. -func (m InternalUserMutation) Client() *Client { +func (m SupportedLocaleMutation) Client() *Client { client := &Client{config: m.config} client.init() return client @@ -106,7 +6945,7 @@ func (m InternalUserMutation) Client() *Client { // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. -func (m InternalUserMutation) Tx() (*Tx, error) { +func (m SupportedLocaleMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("ent: mutation is not running in a transaction") } @@ -116,14 +6955,14 @@ func (m InternalUserMutation) Tx() (*Tx, error) { } // SetID sets the value of the id field. Note that this -// operation is only accepted on creation of InternalUser entities. -func (m *InternalUserMutation) SetID(id uuid.UUID) { +// operation is only accepted on creation of SupportedLocale entities. +func (m *SupportedLocaleMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. -func (m *InternalUserMutation) ID() (id uuid.UUID, exists bool) { +func (m *SupportedLocaleMutation) ID() (id string, exists bool) { if m.id == nil { return } @@ -134,138 +6973,230 @@ func (m *InternalUserMutation) ID() (id uuid.UUID, exists bool) { // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. -func (m *InternalUserMutation) IDs(ctx context.Context) ([]uuid.UUID, error) { +func (m *SupportedLocaleMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { - return []uuid.UUID{id}, nil + return []string{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): - return m.Client().InternalUser.Query().Where(m.predicates...).IDs(ctx) + return m.Client().SupportedLocale.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } -// SetClerkSubject sets the "clerk_subject" field. -func (m *InternalUserMutation) SetClerkSubject(s string) { - m.clerk_subject = &s +// SetActive sets the "active" field. +func (m *SupportedLocaleMutation) SetActive(b bool) { + m.active = &b } -// ClerkSubject returns the value of the "clerk_subject" field in the mutation. -func (m *InternalUserMutation) ClerkSubject() (r string, exists bool) { - v := m.clerk_subject +// Active returns the value of the "active" field in the mutation. +func (m *SupportedLocaleMutation) Active() (r bool, exists bool) { + v := m.active if v == nil { return } return *v, true } -// OldClerkSubject returns the old "clerk_subject" field's value of the InternalUser entity. -// If the InternalUser object wasn't provided to the builder, the object is fetched from the database. +// OldActive returns the old "active" field's value of the SupportedLocale entity. +// If the SupportedLocale object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *InternalUserMutation) OldClerkSubject(ctx context.Context) (v string, err error) { +func (m *SupportedLocaleMutation) OldActive(ctx context.Context) (v bool, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldClerkSubject is only allowed on UpdateOne operations") + return v, errors.New("OldActive is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldClerkSubject requires an ID field in the mutation") + return v, errors.New("OldActive requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldClerkSubject: %w", err) + return v, fmt.Errorf("querying old value for OldActive: %w", err) } - return oldValue.ClerkSubject, nil + return oldValue.Active, nil } -// ResetClerkSubject resets all changes to the "clerk_subject" field. -func (m *InternalUserMutation) ResetClerkSubject() { - m.clerk_subject = nil +// ResetActive resets all changes to the "active" field. +func (m *SupportedLocaleMutation) ResetActive() { + m.active = nil } -// SetCreatedAt sets the "created_at" field. -func (m *InternalUserMutation) SetCreatedAt(t time.Time) { - m.created_at = &t +// SetSortOrder sets the "sort_order" field. +func (m *SupportedLocaleMutation) SetSortOrder(i int) { + m.sort_order = &i + m.addsort_order = nil } -// CreatedAt returns the value of the "created_at" field in the mutation. -func (m *InternalUserMutation) CreatedAt() (r time.Time, exists bool) { - v := m.created_at +// SortOrder returns the value of the "sort_order" field in the mutation. +func (m *SupportedLocaleMutation) SortOrder() (r int, exists bool) { + v := m.sort_order if v == nil { return } return *v, true } -// OldCreatedAt returns the old "created_at" field's value of the InternalUser entity. -// If the InternalUser object wasn't provided to the builder, the object is fetched from the database. +// OldSortOrder returns the old "sort_order" field's value of the SupportedLocale entity. +// If the SupportedLocale object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *InternalUserMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { +func (m *SupportedLocaleMutation) OldSortOrder(ctx context.Context) (v int, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + return v, errors.New("OldSortOrder is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldCreatedAt requires an ID field in the mutation") + return v, errors.New("OldSortOrder requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + return v, fmt.Errorf("querying old value for OldSortOrder: %w", err) } - return oldValue.CreatedAt, nil -} - -// ResetCreatedAt resets all changes to the "created_at" field. -func (m *InternalUserMutation) ResetCreatedAt() { - m.created_at = nil + return oldValue.SortOrder, nil } -// SetUpdatedAt sets the "updated_at" field. -func (m *InternalUserMutation) SetUpdatedAt(t time.Time) { - m.updated_at = &t +// AddSortOrder adds i to the "sort_order" field. +func (m *SupportedLocaleMutation) AddSortOrder(i int) { + if m.addsort_order != nil { + *m.addsort_order += i + } else { + m.addsort_order = &i + } } -// UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *InternalUserMutation) UpdatedAt() (r time.Time, exists bool) { - v := m.updated_at +// AddedSortOrder returns the value that was added to the "sort_order" field in this mutation. +func (m *SupportedLocaleMutation) AddedSortOrder() (r int, exists bool) { + v := m.addsort_order if v == nil { return } return *v, true } -// OldUpdatedAt returns the old "updated_at" field's value of the InternalUser entity. -// If the InternalUser object wasn't provided to the builder, the object is fetched from the database. -// An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *InternalUserMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") +// ResetSortOrder resets all changes to the "sort_order" field. +func (m *SupportedLocaleMutation) ResetSortOrder() { + m.sort_order = nil + m.addsort_order = nil +} + +// AddTranslatedCategoryIDs adds the "translated_categories" edge to the ServiceCategory entity by ids. +func (m *SupportedLocaleMutation) AddTranslatedCategoryIDs(ids ...uuid.UUID) { + if m.translated_categories == nil { + m.translated_categories = make(map[uuid.UUID]struct{}) } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + for i := range ids { + m.translated_categories[ids[i]] = struct{}{} } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) +} + +// ClearTranslatedCategories clears the "translated_categories" edge to the ServiceCategory entity. +func (m *SupportedLocaleMutation) ClearTranslatedCategories() { + m.clearedtranslated_categories = true +} + +// TranslatedCategoriesCleared reports if the "translated_categories" edge to the ServiceCategory entity was cleared. +func (m *SupportedLocaleMutation) TranslatedCategoriesCleared() bool { + return m.clearedtranslated_categories +} + +// RemoveTranslatedCategoryIDs removes the "translated_categories" edge to the ServiceCategory entity by IDs. +func (m *SupportedLocaleMutation) RemoveTranslatedCategoryIDs(ids ...uuid.UUID) { + if m.removedtranslated_categories == nil { + m.removedtranslated_categories = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.translated_categories, ids[i]) + m.removedtranslated_categories[ids[i]] = struct{}{} } - return oldValue.UpdatedAt, nil } -// ResetUpdatedAt resets all changes to the "updated_at" field. -func (m *InternalUserMutation) ResetUpdatedAt() { - m.updated_at = nil +// RemovedTranslatedCategories returns the removed IDs of the "translated_categories" edge to the ServiceCategory entity. +func (m *SupportedLocaleMutation) RemovedTranslatedCategoriesIDs() (ids []uuid.UUID) { + for id := range m.removedtranslated_categories { + ids = append(ids, id) + } + return } -// Where appends a list predicates to the InternalUserMutation builder. -func (m *InternalUserMutation) Where(ps ...predicate.InternalUser) { +// TranslatedCategoriesIDs returns the "translated_categories" edge IDs in the mutation. +func (m *SupportedLocaleMutation) TranslatedCategoriesIDs() (ids []uuid.UUID) { + for id := range m.translated_categories { + ids = append(ids, id) + } + return +} + +// ResetTranslatedCategories resets all changes to the "translated_categories" edge. +func (m *SupportedLocaleMutation) ResetTranslatedCategories() { + m.translated_categories = nil + m.clearedtranslated_categories = false + m.removedtranslated_categories = nil +} + +// AddTranslatedLanguageIDs adds the "translated_languages" edge to the SpokenLanguage entity by ids. +func (m *SupportedLocaleMutation) AddTranslatedLanguageIDs(ids ...string) { + if m.translated_languages == nil { + m.translated_languages = make(map[string]struct{}) + } + for i := range ids { + m.translated_languages[ids[i]] = struct{}{} + } +} + +// ClearTranslatedLanguages clears the "translated_languages" edge to the SpokenLanguage entity. +func (m *SupportedLocaleMutation) ClearTranslatedLanguages() { + m.clearedtranslated_languages = true +} + +// TranslatedLanguagesCleared reports if the "translated_languages" edge to the SpokenLanguage entity was cleared. +func (m *SupportedLocaleMutation) TranslatedLanguagesCleared() bool { + return m.clearedtranslated_languages +} + +// RemoveTranslatedLanguageIDs removes the "translated_languages" edge to the SpokenLanguage entity by IDs. +func (m *SupportedLocaleMutation) RemoveTranslatedLanguageIDs(ids ...string) { + if m.removedtranslated_languages == nil { + m.removedtranslated_languages = make(map[string]struct{}) + } + for i := range ids { + delete(m.translated_languages, ids[i]) + m.removedtranslated_languages[ids[i]] = struct{}{} + } +} + +// RemovedTranslatedLanguages returns the removed IDs of the "translated_languages" edge to the SpokenLanguage entity. +func (m *SupportedLocaleMutation) RemovedTranslatedLanguagesIDs() (ids []string) { + for id := range m.removedtranslated_languages { + ids = append(ids, id) + } + return +} + +// TranslatedLanguagesIDs returns the "translated_languages" edge IDs in the mutation. +func (m *SupportedLocaleMutation) TranslatedLanguagesIDs() (ids []string) { + for id := range m.translated_languages { + ids = append(ids, id) + } + return +} + +// ResetTranslatedLanguages resets all changes to the "translated_languages" edge. +func (m *SupportedLocaleMutation) ResetTranslatedLanguages() { + m.translated_languages = nil + m.clearedtranslated_languages = false + m.removedtranslated_languages = nil +} + +// Where appends a list predicates to the SupportedLocaleMutation builder. +func (m *SupportedLocaleMutation) Where(ps ...predicate.SupportedLocale) { m.predicates = append(m.predicates, ps...) } -// WhereP appends storage-level predicates to the InternalUserMutation builder. Using this method, +// WhereP appends storage-level predicates to the SupportedLocaleMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. -func (m *InternalUserMutation) WhereP(ps ...func(*sql.Selector)) { - p := make([]predicate.InternalUser, len(ps)) +func (m *SupportedLocaleMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.SupportedLocale, len(ps)) for i := range ps { p[i] = ps[i] } @@ -273,33 +7204,30 @@ func (m *InternalUserMutation) WhereP(ps ...func(*sql.Selector)) { } // Op returns the operation name. -func (m *InternalUserMutation) Op() Op { +func (m *SupportedLocaleMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. -func (m *InternalUserMutation) SetOp(op Op) { +func (m *SupportedLocaleMutation) SetOp(op Op) { m.op = op } -// Type returns the node type of this mutation (InternalUser). -func (m *InternalUserMutation) Type() string { +// Type returns the node type of this mutation (SupportedLocale). +func (m *SupportedLocaleMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). -func (m *InternalUserMutation) Fields() []string { - fields := make([]string, 0, 3) - if m.clerk_subject != nil { - fields = append(fields, internaluser.FieldClerkSubject) - } - if m.created_at != nil { - fields = append(fields, internaluser.FieldCreatedAt) +func (m *SupportedLocaleMutation) Fields() []string { + fields := make([]string, 0, 2) + if m.active != nil { + fields = append(fields, supportedlocale.FieldActive) } - if m.updated_at != nil { - fields = append(fields, internaluser.FieldUpdatedAt) + if m.sort_order != nil { + fields = append(fields, supportedlocale.FieldSortOrder) } return fields } @@ -307,14 +7235,12 @@ func (m *InternalUserMutation) Fields() []string { // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. -func (m *InternalUserMutation) Field(name string) (ent.Value, bool) { +func (m *SupportedLocaleMutation) Field(name string) (ent.Value, bool) { switch name { - case internaluser.FieldClerkSubject: - return m.ClerkSubject() - case internaluser.FieldCreatedAt: - return m.CreatedAt() - case internaluser.FieldUpdatedAt: - return m.UpdatedAt() + case supportedlocale.FieldActive: + return m.Active() + case supportedlocale.FieldSortOrder: + return m.SortOrder() } return nil, false } @@ -322,152 +7248,217 @@ func (m *InternalUserMutation) Field(name string) (ent.Value, bool) { // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. -func (m *InternalUserMutation) OldField(ctx context.Context, name string) (ent.Value, error) { +func (m *SupportedLocaleMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { - case internaluser.FieldClerkSubject: - return m.OldClerkSubject(ctx) - case internaluser.FieldCreatedAt: - return m.OldCreatedAt(ctx) - case internaluser.FieldUpdatedAt: - return m.OldUpdatedAt(ctx) + case supportedlocale.FieldActive: + return m.OldActive(ctx) + case supportedlocale.FieldSortOrder: + return m.OldSortOrder(ctx) } - return nil, fmt.Errorf("unknown InternalUser field %s", name) + return nil, fmt.Errorf("unknown SupportedLocale field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. -func (m *InternalUserMutation) SetField(name string, value ent.Value) error { +func (m *SupportedLocaleMutation) SetField(name string, value ent.Value) error { switch name { - case internaluser.FieldClerkSubject: - v, ok := value.(string) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetClerkSubject(v) - return nil - case internaluser.FieldCreatedAt: - v, ok := value.(time.Time) + case supportedlocale.FieldActive: + v, ok := value.(bool) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetCreatedAt(v) + m.SetActive(v) return nil - case internaluser.FieldUpdatedAt: - v, ok := value.(time.Time) + case supportedlocale.FieldSortOrder: + v, ok := value.(int) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetUpdatedAt(v) + m.SetSortOrder(v) return nil } - return fmt.Errorf("unknown InternalUser field %s", name) + return fmt.Errorf("unknown SupportedLocale field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. -func (m *InternalUserMutation) AddedFields() []string { - return nil +func (m *SupportedLocaleMutation) AddedFields() []string { + var fields []string + if m.addsort_order != nil { + fields = append(fields, supportedlocale.FieldSortOrder) + } + return fields } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. -func (m *InternalUserMutation) AddedField(name string) (ent.Value, bool) { +func (m *SupportedLocaleMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case supportedlocale.FieldSortOrder: + return m.AddedSortOrder() + } return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. -func (m *InternalUserMutation) AddField(name string, value ent.Value) error { +func (m *SupportedLocaleMutation) AddField(name string, value ent.Value) error { switch name { + case supportedlocale.FieldSortOrder: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddSortOrder(v) + return nil } - return fmt.Errorf("unknown InternalUser numeric field %s", name) + return fmt.Errorf("unknown SupportedLocale numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. -func (m *InternalUserMutation) ClearedFields() []string { +func (m *SupportedLocaleMutation) ClearedFields() []string { return nil } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. -func (m *InternalUserMutation) FieldCleared(name string) bool { +func (m *SupportedLocaleMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. -func (m *InternalUserMutation) ClearField(name string) error { - return fmt.Errorf("unknown InternalUser nullable field %s", name) +func (m *SupportedLocaleMutation) ClearField(name string) error { + return fmt.Errorf("unknown SupportedLocale nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. -func (m *InternalUserMutation) ResetField(name string) error { +func (m *SupportedLocaleMutation) ResetField(name string) error { switch name { - case internaluser.FieldClerkSubject: - m.ResetClerkSubject() - return nil - case internaluser.FieldCreatedAt: - m.ResetCreatedAt() + case supportedlocale.FieldActive: + m.ResetActive() return nil - case internaluser.FieldUpdatedAt: - m.ResetUpdatedAt() + case supportedlocale.FieldSortOrder: + m.ResetSortOrder() return nil } - return fmt.Errorf("unknown InternalUser field %s", name) + return fmt.Errorf("unknown SupportedLocale field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. -func (m *InternalUserMutation) AddedEdges() []string { - edges := make([]string, 0, 0) +func (m *SupportedLocaleMutation) AddedEdges() []string { + edges := make([]string, 0, 2) + if m.translated_categories != nil { + edges = append(edges, supportedlocale.EdgeTranslatedCategories) + } + if m.translated_languages != nil { + edges = append(edges, supportedlocale.EdgeTranslatedLanguages) + } return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. -func (m *InternalUserMutation) AddedIDs(name string) []ent.Value { +func (m *SupportedLocaleMutation) AddedIDs(name string) []ent.Value { + switch name { + case supportedlocale.EdgeTranslatedCategories: + ids := make([]ent.Value, 0, len(m.translated_categories)) + for id := range m.translated_categories { + ids = append(ids, id) + } + return ids + case supportedlocale.EdgeTranslatedLanguages: + ids := make([]ent.Value, 0, len(m.translated_languages)) + for id := range m.translated_languages { + ids = append(ids, id) + } + return ids + } return nil } // RemovedEdges returns all edge names that were removed in this mutation. -func (m *InternalUserMutation) RemovedEdges() []string { - edges := make([]string, 0, 0) +func (m *SupportedLocaleMutation) RemovedEdges() []string { + edges := make([]string, 0, 2) + if m.removedtranslated_categories != nil { + edges = append(edges, supportedlocale.EdgeTranslatedCategories) + } + if m.removedtranslated_languages != nil { + edges = append(edges, supportedlocale.EdgeTranslatedLanguages) + } return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. -func (m *InternalUserMutation) RemovedIDs(name string) []ent.Value { +func (m *SupportedLocaleMutation) RemovedIDs(name string) []ent.Value { + switch name { + case supportedlocale.EdgeTranslatedCategories: + ids := make([]ent.Value, 0, len(m.removedtranslated_categories)) + for id := range m.removedtranslated_categories { + ids = append(ids, id) + } + return ids + case supportedlocale.EdgeTranslatedLanguages: + ids := make([]ent.Value, 0, len(m.removedtranslated_languages)) + for id := range m.removedtranslated_languages { + ids = append(ids, id) + } + return ids + } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. -func (m *InternalUserMutation) ClearedEdges() []string { - edges := make([]string, 0, 0) +func (m *SupportedLocaleMutation) ClearedEdges() []string { + edges := make([]string, 0, 2) + if m.clearedtranslated_categories { + edges = append(edges, supportedlocale.EdgeTranslatedCategories) + } + if m.clearedtranslated_languages { + edges = append(edges, supportedlocale.EdgeTranslatedLanguages) + } return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. -func (m *InternalUserMutation) EdgeCleared(name string) bool { +func (m *SupportedLocaleMutation) EdgeCleared(name string) bool { + switch name { + case supportedlocale.EdgeTranslatedCategories: + return m.clearedtranslated_categories + case supportedlocale.EdgeTranslatedLanguages: + return m.clearedtranslated_languages + } return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. -func (m *InternalUserMutation) ClearEdge(name string) error { - return fmt.Errorf("unknown InternalUser unique edge %s", name) +func (m *SupportedLocaleMutation) ClearEdge(name string) error { + switch name { + } + return fmt.Errorf("unknown SupportedLocale unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. -func (m *InternalUserMutation) ResetEdge(name string) error { - return fmt.Errorf("unknown InternalUser edge %s", name) +func (m *SupportedLocaleMutation) ResetEdge(name string) error { + switch name { + case supportedlocale.EdgeTranslatedCategories: + m.ResetTranslatedCategories() + return nil + case supportedlocale.EdgeTranslatedLanguages: + m.ResetTranslatedLanguages() + return nil + } + return fmt.Errorf("unknown SupportedLocale edge %s", name) } // UserAccountMutation represents an operation that mutates the UserAccount nodes in the graph. diff --git a/backend/ent/predicate/predicate.go b/backend/ent/predicate/predicate.go index 8315047..e481245 100644 --- a/backend/ent/predicate/predicate.go +++ b/backend/ent/predicate/predicate.go @@ -6,8 +6,38 @@ import ( "entgo.io/ent/dialect/sql" ) +// AdministrativeArea is the predicate function for administrativearea builders. +type AdministrativeArea func(*sql.Selector) + // InternalUser is the predicate function for internaluser builders. type InternalUser func(*sql.Selector) +// Locality is the predicate function for locality builders. +type Locality func(*sql.Selector) + +// ProviderProfile is the predicate function for providerprofile builders. +type ProviderProfile func(*sql.Selector) + +// ProviderServiceLocality is the predicate function for providerservicelocality builders. +type ProviderServiceLocality func(*sql.Selector) + +// ProviderSpokenLanguage is the predicate function for providerspokenlanguage builders. +type ProviderSpokenLanguage func(*sql.Selector) + +// ServiceCategory is the predicate function for servicecategory builders. +type ServiceCategory func(*sql.Selector) + +// ServiceCategoryTranslation is the predicate function for servicecategorytranslation builders. +type ServiceCategoryTranslation func(*sql.Selector) + +// SpokenLanguage is the predicate function for spokenlanguage builders. +type SpokenLanguage func(*sql.Selector) + +// SpokenLanguageTranslation is the predicate function for spokenlanguagetranslation builders. +type SpokenLanguageTranslation func(*sql.Selector) + +// SupportedLocale is the predicate function for supportedlocale builders. +type SupportedLocale func(*sql.Selector) + // UserAccount is the predicate function for useraccount builders. type UserAccount func(*sql.Selector) diff --git a/backend/ent/providerprofile.go b/backend/ent/providerprofile.go new file mode 100644 index 0000000..00eff1d --- /dev/null +++ b/backend/ent/providerprofile.go @@ -0,0 +1,284 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/google/uuid" +) + +// ProviderProfile is the model entity for the ProviderProfile schema. +type ProviderProfile struct { + config `json:"-"` + // ID of the ent. + ID uuid.UUID `json:"id,omitempty"` + // DisplayName holds the value of the "display_name" field. + DisplayName string `json:"display_name,omitempty"` + // ProviderType holds the value of the "provider_type" field. + ProviderType string `json:"provider_type,omitempty"` + // Bio holds the value of the "bio" field. + Bio string `json:"bio,omitempty"` + // PrimaryLocalityID holds the value of the "primary_locality_id" field. + PrimaryLocalityID uuid.UUID `json:"primary_locality_id,omitempty"` + // MaxTravelDistanceKm holds the value of the "max_travel_distance_km" field. + MaxTravelDistanceKm int `json:"max_travel_distance_km,omitempty"` + // TravelsToCustomer holds the value of the "travels_to_customer" field. + TravelsToCustomer bool `json:"travels_to_customer,omitempty"` + // ReceivesCustomer holds the value of the "receives_customer" field. + ReceivesCustomer bool `json:"receives_customer,omitempty"` + // RemoteServices holds the value of the "remote_services" field. + RemoteServices bool `json:"remote_services,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the ProviderProfileQuery when eager-loading is set. + Edges ProviderProfileEdges `json:"edges"` + selectValues sql.SelectValues +} + +// ProviderProfileEdges holds the relations/edges for other nodes in the graph. +type ProviderProfileEdges struct { + // ServiceLocalities holds the value of the service_localities edge. + ServiceLocalities []*Locality `json:"service_localities,omitempty"` + // SpokenLanguages holds the value of the spoken_languages edge. + SpokenLanguages []*SpokenLanguage `json:"spoken_languages,omitempty"` + // ServiceLocalityLinks holds the value of the service_locality_links edge. + ServiceLocalityLinks []*ProviderServiceLocality `json:"service_locality_links,omitempty"` + // SpokenLanguageLinks holds the value of the spoken_language_links edge. + SpokenLanguageLinks []*ProviderSpokenLanguage `json:"spoken_language_links,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [4]bool +} + +// ServiceLocalitiesOrErr returns the ServiceLocalities value or an error if the edge +// was not loaded in eager-loading. +func (e ProviderProfileEdges) ServiceLocalitiesOrErr() ([]*Locality, error) { + if e.loadedTypes[0] { + return e.ServiceLocalities, nil + } + return nil, &NotLoadedError{edge: "service_localities"} +} + +// SpokenLanguagesOrErr returns the SpokenLanguages value or an error if the edge +// was not loaded in eager-loading. +func (e ProviderProfileEdges) SpokenLanguagesOrErr() ([]*SpokenLanguage, error) { + if e.loadedTypes[1] { + return e.SpokenLanguages, nil + } + return nil, &NotLoadedError{edge: "spoken_languages"} +} + +// ServiceLocalityLinksOrErr returns the ServiceLocalityLinks value or an error if the edge +// was not loaded in eager-loading. +func (e ProviderProfileEdges) ServiceLocalityLinksOrErr() ([]*ProviderServiceLocality, error) { + if e.loadedTypes[2] { + return e.ServiceLocalityLinks, nil + } + return nil, &NotLoadedError{edge: "service_locality_links"} +} + +// SpokenLanguageLinksOrErr returns the SpokenLanguageLinks value or an error if the edge +// was not loaded in eager-loading. +func (e ProviderProfileEdges) SpokenLanguageLinksOrErr() ([]*ProviderSpokenLanguage, error) { + if e.loadedTypes[3] { + return e.SpokenLanguageLinks, nil + } + return nil, &NotLoadedError{edge: "spoken_language_links"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*ProviderProfile) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case providerprofile.FieldTravelsToCustomer, providerprofile.FieldReceivesCustomer, providerprofile.FieldRemoteServices: + values[i] = new(sql.NullBool) + case providerprofile.FieldMaxTravelDistanceKm: + values[i] = new(sql.NullInt64) + case providerprofile.FieldDisplayName, providerprofile.FieldProviderType, providerprofile.FieldBio: + values[i] = new(sql.NullString) + case providerprofile.FieldCreatedAt, providerprofile.FieldUpdatedAt: + values[i] = new(sql.NullTime) + case providerprofile.FieldID, providerprofile.FieldPrimaryLocalityID: + values[i] = new(uuid.UUID) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the ProviderProfile fields. +func (_m *ProviderProfile) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case providerprofile.FieldID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value != nil { + _m.ID = *value + } + case providerprofile.FieldDisplayName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field display_name", values[i]) + } else if value.Valid { + _m.DisplayName = value.String + } + case providerprofile.FieldProviderType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field provider_type", values[i]) + } else if value.Valid { + _m.ProviderType = value.String + } + case providerprofile.FieldBio: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field bio", values[i]) + } else if value.Valid { + _m.Bio = value.String + } + case providerprofile.FieldPrimaryLocalityID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field primary_locality_id", values[i]) + } else if value != nil { + _m.PrimaryLocalityID = *value + } + case providerprofile.FieldMaxTravelDistanceKm: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field max_travel_distance_km", values[i]) + } else if value.Valid { + _m.MaxTravelDistanceKm = int(value.Int64) + } + case providerprofile.FieldTravelsToCustomer: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field travels_to_customer", values[i]) + } else if value.Valid { + _m.TravelsToCustomer = value.Bool + } + case providerprofile.FieldReceivesCustomer: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field receives_customer", values[i]) + } else if value.Valid { + _m.ReceivesCustomer = value.Bool + } + case providerprofile.FieldRemoteServices: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field remote_services", values[i]) + } else if value.Valid { + _m.RemoteServices = value.Bool + } + case providerprofile.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + _m.CreatedAt = value.Time + } + case providerprofile.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + _m.UpdatedAt = value.Time + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the ProviderProfile. +// This includes values selected through modifiers, order, etc. +func (_m *ProviderProfile) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QueryServiceLocalities queries the "service_localities" edge of the ProviderProfile entity. +func (_m *ProviderProfile) QueryServiceLocalities() *LocalityQuery { + return NewProviderProfileClient(_m.config).QueryServiceLocalities(_m) +} + +// QuerySpokenLanguages queries the "spoken_languages" edge of the ProviderProfile entity. +func (_m *ProviderProfile) QuerySpokenLanguages() *SpokenLanguageQuery { + return NewProviderProfileClient(_m.config).QuerySpokenLanguages(_m) +} + +// QueryServiceLocalityLinks queries the "service_locality_links" edge of the ProviderProfile entity. +func (_m *ProviderProfile) QueryServiceLocalityLinks() *ProviderServiceLocalityQuery { + return NewProviderProfileClient(_m.config).QueryServiceLocalityLinks(_m) +} + +// QuerySpokenLanguageLinks queries the "spoken_language_links" edge of the ProviderProfile entity. +func (_m *ProviderProfile) QuerySpokenLanguageLinks() *ProviderSpokenLanguageQuery { + return NewProviderProfileClient(_m.config).QuerySpokenLanguageLinks(_m) +} + +// Update returns a builder for updating this ProviderProfile. +// Note that you need to call ProviderProfile.Unwrap() before calling this method if this ProviderProfile +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *ProviderProfile) Update() *ProviderProfileUpdateOne { + return NewProviderProfileClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the ProviderProfile entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *ProviderProfile) Unwrap() *ProviderProfile { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: ProviderProfile is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *ProviderProfile) String() string { + var builder strings.Builder + builder.WriteString("ProviderProfile(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("display_name=") + builder.WriteString(_m.DisplayName) + builder.WriteString(", ") + builder.WriteString("provider_type=") + builder.WriteString(_m.ProviderType) + builder.WriteString(", ") + builder.WriteString("bio=") + builder.WriteString(_m.Bio) + builder.WriteString(", ") + builder.WriteString("primary_locality_id=") + builder.WriteString(fmt.Sprintf("%v", _m.PrimaryLocalityID)) + builder.WriteString(", ") + builder.WriteString("max_travel_distance_km=") + builder.WriteString(fmt.Sprintf("%v", _m.MaxTravelDistanceKm)) + builder.WriteString(", ") + builder.WriteString("travels_to_customer=") + builder.WriteString(fmt.Sprintf("%v", _m.TravelsToCustomer)) + builder.WriteString(", ") + builder.WriteString("receives_customer=") + builder.WriteString(fmt.Sprintf("%v", _m.ReceivesCustomer)) + builder.WriteString(", ") + builder.WriteString("remote_services=") + builder.WriteString(fmt.Sprintf("%v", _m.RemoteServices)) + builder.WriteString(", ") + builder.WriteString("created_at=") + builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(_m.UpdatedAt.Format(time.ANSIC)) + builder.WriteByte(')') + return builder.String() +} + +// ProviderProfiles is a parsable slice of ProviderProfile. +type ProviderProfiles []*ProviderProfile diff --git a/backend/ent/providerprofile/providerprofile.go b/backend/ent/providerprofile/providerprofile.go new file mode 100644 index 0000000..dc56bb3 --- /dev/null +++ b/backend/ent/providerprofile/providerprofile.go @@ -0,0 +1,274 @@ +// Code generated by ent, DO NOT EDIT. + +package providerprofile + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the providerprofile type in the database. + Label = "provider_profile" + // FieldID holds the string denoting the id field in the database. + FieldID = "internal_user_id" + // FieldDisplayName holds the string denoting the display_name field in the database. + FieldDisplayName = "display_name" + // FieldProviderType holds the string denoting the provider_type field in the database. + FieldProviderType = "provider_type" + // FieldBio holds the string denoting the bio field in the database. + FieldBio = "bio" + // FieldPrimaryLocalityID holds the string denoting the primary_locality_id field in the database. + FieldPrimaryLocalityID = "primary_locality_id" + // FieldMaxTravelDistanceKm holds the string denoting the max_travel_distance_km field in the database. + FieldMaxTravelDistanceKm = "max_travel_distance_km" + // FieldTravelsToCustomer holds the string denoting the travels_to_customer field in the database. + FieldTravelsToCustomer = "travels_to_customer" + // FieldReceivesCustomer holds the string denoting the receives_customer field in the database. + FieldReceivesCustomer = "receives_customer" + // FieldRemoteServices holds the string denoting the remote_services field in the database. + FieldRemoteServices = "remote_services" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // EdgeServiceLocalities holds the string denoting the service_localities edge name in mutations. + EdgeServiceLocalities = "service_localities" + // EdgeSpokenLanguages holds the string denoting the spoken_languages edge name in mutations. + EdgeSpokenLanguages = "spoken_languages" + // EdgeServiceLocalityLinks holds the string denoting the service_locality_links edge name in mutations. + EdgeServiceLocalityLinks = "service_locality_links" + // EdgeSpokenLanguageLinks holds the string denoting the spoken_language_links edge name in mutations. + EdgeSpokenLanguageLinks = "spoken_language_links" + // LocalityFieldID holds the string denoting the ID field of the Locality. + LocalityFieldID = "id" + // SpokenLanguageFieldID holds the string denoting the ID field of the SpokenLanguage. + SpokenLanguageFieldID = "id" + // Table holds the table name of the providerprofile in the database. + Table = "provider_profiles" + // ServiceLocalitiesTable is the table that holds the service_localities relation/edge. The primary key declared below. + ServiceLocalitiesTable = "provider_service_localities" + // ServiceLocalitiesInverseTable is the table name for the Locality entity. + // It exists in this package in order to avoid circular dependency with the "locality" package. + ServiceLocalitiesInverseTable = "localities" + // SpokenLanguagesTable is the table that holds the spoken_languages relation/edge. The primary key declared below. + SpokenLanguagesTable = "provider_spoken_languages" + // SpokenLanguagesInverseTable is the table name for the SpokenLanguage entity. + // It exists in this package in order to avoid circular dependency with the "spokenlanguage" package. + SpokenLanguagesInverseTable = "spoken_languages" + // ServiceLocalityLinksTable is the table that holds the service_locality_links relation/edge. + ServiceLocalityLinksTable = "provider_service_localities" + // ServiceLocalityLinksInverseTable is the table name for the ProviderServiceLocality entity. + // It exists in this package in order to avoid circular dependency with the "providerservicelocality" package. + ServiceLocalityLinksInverseTable = "provider_service_localities" + // ServiceLocalityLinksColumn is the table column denoting the service_locality_links relation/edge. + ServiceLocalityLinksColumn = "internal_user_id" + // SpokenLanguageLinksTable is the table that holds the spoken_language_links relation/edge. + SpokenLanguageLinksTable = "provider_spoken_languages" + // SpokenLanguageLinksInverseTable is the table name for the ProviderSpokenLanguage entity. + // It exists in this package in order to avoid circular dependency with the "providerspokenlanguage" package. + SpokenLanguageLinksInverseTable = "provider_spoken_languages" + // SpokenLanguageLinksColumn is the table column denoting the spoken_language_links relation/edge. + SpokenLanguageLinksColumn = "internal_user_id" +) + +// Columns holds all SQL columns for providerprofile fields. +var Columns = []string{ + FieldID, + FieldDisplayName, + FieldProviderType, + FieldBio, + FieldPrimaryLocalityID, + FieldMaxTravelDistanceKm, + FieldTravelsToCustomer, + FieldReceivesCustomer, + FieldRemoteServices, + FieldCreatedAt, + FieldUpdatedAt, +} + +var ( + // ServiceLocalitiesPrimaryKey and ServiceLocalitiesColumn2 are the table columns denoting the + // primary key for the service_localities relation (M2M). + ServiceLocalitiesPrimaryKey = []string{"internal_user_id", "locality_id"} + // SpokenLanguagesPrimaryKey and SpokenLanguagesColumn2 are the table columns denoting the + // primary key for the spoken_languages relation (M2M). + SpokenLanguagesPrimaryKey = []string{"internal_user_id", "language_code"} +) + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DisplayNameValidator is a validator for the "display_name" field. It is called by the builders before save. + DisplayNameValidator func(string) error + // ProviderTypeValidator is a validator for the "provider_type" field. It is called by the builders before save. + ProviderTypeValidator func(string) error + // BioValidator is a validator for the "bio" field. It is called by the builders before save. + BioValidator func(string) error + // MaxTravelDistanceKmValidator is a validator for the "max_travel_distance_km" field. It is called by the builders before save. + MaxTravelDistanceKmValidator func(int) error + // DefaultTravelsToCustomer holds the default value on creation for the "travels_to_customer" field. + DefaultTravelsToCustomer bool + // DefaultReceivesCustomer holds the default value on creation for the "receives_customer" field. + DefaultReceivesCustomer bool + // DefaultRemoteServices holds the default value on creation for the "remote_services" field. + DefaultRemoteServices bool + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time +) + +// OrderOption defines the ordering options for the ProviderProfile queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByDisplayName orders the results by the display_name field. +func ByDisplayName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldDisplayName, opts...).ToFunc() +} + +// ByProviderType orders the results by the provider_type field. +func ByProviderType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldProviderType, opts...).ToFunc() +} + +// ByBio orders the results by the bio field. +func ByBio(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldBio, opts...).ToFunc() +} + +// ByPrimaryLocalityID orders the results by the primary_locality_id field. +func ByPrimaryLocalityID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPrimaryLocalityID, opts...).ToFunc() +} + +// ByMaxTravelDistanceKm orders the results by the max_travel_distance_km field. +func ByMaxTravelDistanceKm(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldMaxTravelDistanceKm, opts...).ToFunc() +} + +// ByTravelsToCustomer orders the results by the travels_to_customer field. +func ByTravelsToCustomer(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldTravelsToCustomer, opts...).ToFunc() +} + +// ByReceivesCustomer orders the results by the receives_customer field. +func ByReceivesCustomer(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldReceivesCustomer, opts...).ToFunc() +} + +// ByRemoteServices orders the results by the remote_services field. +func ByRemoteServices(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRemoteServices, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByServiceLocalitiesCount orders the results by service_localities count. +func ByServiceLocalitiesCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newServiceLocalitiesStep(), opts...) + } +} + +// ByServiceLocalities orders the results by service_localities terms. +func ByServiceLocalities(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newServiceLocalitiesStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// BySpokenLanguagesCount orders the results by spoken_languages count. +func BySpokenLanguagesCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newSpokenLanguagesStep(), opts...) + } +} + +// BySpokenLanguages orders the results by spoken_languages terms. +func BySpokenLanguages(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newSpokenLanguagesStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByServiceLocalityLinksCount orders the results by service_locality_links count. +func ByServiceLocalityLinksCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newServiceLocalityLinksStep(), opts...) + } +} + +// ByServiceLocalityLinks orders the results by service_locality_links terms. +func ByServiceLocalityLinks(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newServiceLocalityLinksStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// BySpokenLanguageLinksCount orders the results by spoken_language_links count. +func BySpokenLanguageLinksCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newSpokenLanguageLinksStep(), opts...) + } +} + +// BySpokenLanguageLinks orders the results by spoken_language_links terms. +func BySpokenLanguageLinks(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newSpokenLanguageLinksStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} +func newServiceLocalitiesStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ServiceLocalitiesInverseTable, LocalityFieldID), + sqlgraph.Edge(sqlgraph.M2M, false, ServiceLocalitiesTable, ServiceLocalitiesPrimaryKey...), + ) +} +func newSpokenLanguagesStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(SpokenLanguagesInverseTable, SpokenLanguageFieldID), + sqlgraph.Edge(sqlgraph.M2M, false, SpokenLanguagesTable, SpokenLanguagesPrimaryKey...), + ) +} +func newServiceLocalityLinksStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ServiceLocalityLinksInverseTable, ServiceLocalityLinksColumn), + sqlgraph.Edge(sqlgraph.O2M, true, ServiceLocalityLinksTable, ServiceLocalityLinksColumn), + ) +} +func newSpokenLanguageLinksStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(SpokenLanguageLinksInverseTable, SpokenLanguageLinksColumn), + sqlgraph.Edge(sqlgraph.O2M, true, SpokenLanguageLinksTable, SpokenLanguageLinksColumn), + ) +} diff --git a/backend/ent/providerprofile/where.go b/backend/ent/providerprofile/where.go new file mode 100644 index 0000000..4bfc35d --- /dev/null +++ b/backend/ent/providerprofile/where.go @@ -0,0 +1,599 @@ +// Code generated by ent, DO NOT EDIT. + +package providerprofile + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +// ID filters vertices based on their ID field. +func ID(id uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLTE(FieldID, id)) +} + +// DisplayName applies equality check predicate on the "display_name" field. It's identical to DisplayNameEQ. +func DisplayName(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldDisplayName, v)) +} + +// ProviderType applies equality check predicate on the "provider_type" field. It's identical to ProviderTypeEQ. +func ProviderType(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldProviderType, v)) +} + +// Bio applies equality check predicate on the "bio" field. It's identical to BioEQ. +func Bio(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldBio, v)) +} + +// PrimaryLocalityID applies equality check predicate on the "primary_locality_id" field. It's identical to PrimaryLocalityIDEQ. +func PrimaryLocalityID(v uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldPrimaryLocalityID, v)) +} + +// MaxTravelDistanceKm applies equality check predicate on the "max_travel_distance_km" field. It's identical to MaxTravelDistanceKmEQ. +func MaxTravelDistanceKm(v int) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldMaxTravelDistanceKm, v)) +} + +// TravelsToCustomer applies equality check predicate on the "travels_to_customer" field. It's identical to TravelsToCustomerEQ. +func TravelsToCustomer(v bool) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldTravelsToCustomer, v)) +} + +// ReceivesCustomer applies equality check predicate on the "receives_customer" field. It's identical to ReceivesCustomerEQ. +func ReceivesCustomer(v bool) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldReceivesCustomer, v)) +} + +// RemoteServices applies equality check predicate on the "remote_services" field. It's identical to RemoteServicesEQ. +func RemoteServices(v bool) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldRemoteServices, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// DisplayNameEQ applies the EQ predicate on the "display_name" field. +func DisplayNameEQ(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldDisplayName, v)) +} + +// DisplayNameNEQ applies the NEQ predicate on the "display_name" field. +func DisplayNameNEQ(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNEQ(FieldDisplayName, v)) +} + +// DisplayNameIn applies the In predicate on the "display_name" field. +func DisplayNameIn(vs ...string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldIn(FieldDisplayName, vs...)) +} + +// DisplayNameNotIn applies the NotIn predicate on the "display_name" field. +func DisplayNameNotIn(vs ...string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNotIn(FieldDisplayName, vs...)) +} + +// DisplayNameGT applies the GT predicate on the "display_name" field. +func DisplayNameGT(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGT(FieldDisplayName, v)) +} + +// DisplayNameGTE applies the GTE predicate on the "display_name" field. +func DisplayNameGTE(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGTE(FieldDisplayName, v)) +} + +// DisplayNameLT applies the LT predicate on the "display_name" field. +func DisplayNameLT(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLT(FieldDisplayName, v)) +} + +// DisplayNameLTE applies the LTE predicate on the "display_name" field. +func DisplayNameLTE(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLTE(FieldDisplayName, v)) +} + +// DisplayNameContains applies the Contains predicate on the "display_name" field. +func DisplayNameContains(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldContains(FieldDisplayName, v)) +} + +// DisplayNameHasPrefix applies the HasPrefix predicate on the "display_name" field. +func DisplayNameHasPrefix(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldHasPrefix(FieldDisplayName, v)) +} + +// DisplayNameHasSuffix applies the HasSuffix predicate on the "display_name" field. +func DisplayNameHasSuffix(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldHasSuffix(FieldDisplayName, v)) +} + +// DisplayNameEqualFold applies the EqualFold predicate on the "display_name" field. +func DisplayNameEqualFold(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEqualFold(FieldDisplayName, v)) +} + +// DisplayNameContainsFold applies the ContainsFold predicate on the "display_name" field. +func DisplayNameContainsFold(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldContainsFold(FieldDisplayName, v)) +} + +// ProviderTypeEQ applies the EQ predicate on the "provider_type" field. +func ProviderTypeEQ(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldProviderType, v)) +} + +// ProviderTypeNEQ applies the NEQ predicate on the "provider_type" field. +func ProviderTypeNEQ(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNEQ(FieldProviderType, v)) +} + +// ProviderTypeIn applies the In predicate on the "provider_type" field. +func ProviderTypeIn(vs ...string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldIn(FieldProviderType, vs...)) +} + +// ProviderTypeNotIn applies the NotIn predicate on the "provider_type" field. +func ProviderTypeNotIn(vs ...string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNotIn(FieldProviderType, vs...)) +} + +// ProviderTypeGT applies the GT predicate on the "provider_type" field. +func ProviderTypeGT(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGT(FieldProviderType, v)) +} + +// ProviderTypeGTE applies the GTE predicate on the "provider_type" field. +func ProviderTypeGTE(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGTE(FieldProviderType, v)) +} + +// ProviderTypeLT applies the LT predicate on the "provider_type" field. +func ProviderTypeLT(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLT(FieldProviderType, v)) +} + +// ProviderTypeLTE applies the LTE predicate on the "provider_type" field. +func ProviderTypeLTE(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLTE(FieldProviderType, v)) +} + +// ProviderTypeContains applies the Contains predicate on the "provider_type" field. +func ProviderTypeContains(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldContains(FieldProviderType, v)) +} + +// ProviderTypeHasPrefix applies the HasPrefix predicate on the "provider_type" field. +func ProviderTypeHasPrefix(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldHasPrefix(FieldProviderType, v)) +} + +// ProviderTypeHasSuffix applies the HasSuffix predicate on the "provider_type" field. +func ProviderTypeHasSuffix(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldHasSuffix(FieldProviderType, v)) +} + +// ProviderTypeEqualFold applies the EqualFold predicate on the "provider_type" field. +func ProviderTypeEqualFold(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEqualFold(FieldProviderType, v)) +} + +// ProviderTypeContainsFold applies the ContainsFold predicate on the "provider_type" field. +func ProviderTypeContainsFold(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldContainsFold(FieldProviderType, v)) +} + +// BioEQ applies the EQ predicate on the "bio" field. +func BioEQ(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldBio, v)) +} + +// BioNEQ applies the NEQ predicate on the "bio" field. +func BioNEQ(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNEQ(FieldBio, v)) +} + +// BioIn applies the In predicate on the "bio" field. +func BioIn(vs ...string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldIn(FieldBio, vs...)) +} + +// BioNotIn applies the NotIn predicate on the "bio" field. +func BioNotIn(vs ...string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNotIn(FieldBio, vs...)) +} + +// BioGT applies the GT predicate on the "bio" field. +func BioGT(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGT(FieldBio, v)) +} + +// BioGTE applies the GTE predicate on the "bio" field. +func BioGTE(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGTE(FieldBio, v)) +} + +// BioLT applies the LT predicate on the "bio" field. +func BioLT(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLT(FieldBio, v)) +} + +// BioLTE applies the LTE predicate on the "bio" field. +func BioLTE(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLTE(FieldBio, v)) +} + +// BioContains applies the Contains predicate on the "bio" field. +func BioContains(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldContains(FieldBio, v)) +} + +// BioHasPrefix applies the HasPrefix predicate on the "bio" field. +func BioHasPrefix(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldHasPrefix(FieldBio, v)) +} + +// BioHasSuffix applies the HasSuffix predicate on the "bio" field. +func BioHasSuffix(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldHasSuffix(FieldBio, v)) +} + +// BioEqualFold applies the EqualFold predicate on the "bio" field. +func BioEqualFold(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEqualFold(FieldBio, v)) +} + +// BioContainsFold applies the ContainsFold predicate on the "bio" field. +func BioContainsFold(v string) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldContainsFold(FieldBio, v)) +} + +// PrimaryLocalityIDEQ applies the EQ predicate on the "primary_locality_id" field. +func PrimaryLocalityIDEQ(v uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldPrimaryLocalityID, v)) +} + +// PrimaryLocalityIDNEQ applies the NEQ predicate on the "primary_locality_id" field. +func PrimaryLocalityIDNEQ(v uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNEQ(FieldPrimaryLocalityID, v)) +} + +// PrimaryLocalityIDIn applies the In predicate on the "primary_locality_id" field. +func PrimaryLocalityIDIn(vs ...uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldIn(FieldPrimaryLocalityID, vs...)) +} + +// PrimaryLocalityIDNotIn applies the NotIn predicate on the "primary_locality_id" field. +func PrimaryLocalityIDNotIn(vs ...uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNotIn(FieldPrimaryLocalityID, vs...)) +} + +// PrimaryLocalityIDGT applies the GT predicate on the "primary_locality_id" field. +func PrimaryLocalityIDGT(v uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGT(FieldPrimaryLocalityID, v)) +} + +// PrimaryLocalityIDGTE applies the GTE predicate on the "primary_locality_id" field. +func PrimaryLocalityIDGTE(v uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGTE(FieldPrimaryLocalityID, v)) +} + +// PrimaryLocalityIDLT applies the LT predicate on the "primary_locality_id" field. +func PrimaryLocalityIDLT(v uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLT(FieldPrimaryLocalityID, v)) +} + +// PrimaryLocalityIDLTE applies the LTE predicate on the "primary_locality_id" field. +func PrimaryLocalityIDLTE(v uuid.UUID) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLTE(FieldPrimaryLocalityID, v)) +} + +// MaxTravelDistanceKmEQ applies the EQ predicate on the "max_travel_distance_km" field. +func MaxTravelDistanceKmEQ(v int) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldMaxTravelDistanceKm, v)) +} + +// MaxTravelDistanceKmNEQ applies the NEQ predicate on the "max_travel_distance_km" field. +func MaxTravelDistanceKmNEQ(v int) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNEQ(FieldMaxTravelDistanceKm, v)) +} + +// MaxTravelDistanceKmIn applies the In predicate on the "max_travel_distance_km" field. +func MaxTravelDistanceKmIn(vs ...int) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldIn(FieldMaxTravelDistanceKm, vs...)) +} + +// MaxTravelDistanceKmNotIn applies the NotIn predicate on the "max_travel_distance_km" field. +func MaxTravelDistanceKmNotIn(vs ...int) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNotIn(FieldMaxTravelDistanceKm, vs...)) +} + +// MaxTravelDistanceKmGT applies the GT predicate on the "max_travel_distance_km" field. +func MaxTravelDistanceKmGT(v int) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGT(FieldMaxTravelDistanceKm, v)) +} + +// MaxTravelDistanceKmGTE applies the GTE predicate on the "max_travel_distance_km" field. +func MaxTravelDistanceKmGTE(v int) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGTE(FieldMaxTravelDistanceKm, v)) +} + +// MaxTravelDistanceKmLT applies the LT predicate on the "max_travel_distance_km" field. +func MaxTravelDistanceKmLT(v int) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLT(FieldMaxTravelDistanceKm, v)) +} + +// MaxTravelDistanceKmLTE applies the LTE predicate on the "max_travel_distance_km" field. +func MaxTravelDistanceKmLTE(v int) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLTE(FieldMaxTravelDistanceKm, v)) +} + +// TravelsToCustomerEQ applies the EQ predicate on the "travels_to_customer" field. +func TravelsToCustomerEQ(v bool) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldTravelsToCustomer, v)) +} + +// TravelsToCustomerNEQ applies the NEQ predicate on the "travels_to_customer" field. +func TravelsToCustomerNEQ(v bool) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNEQ(FieldTravelsToCustomer, v)) +} + +// ReceivesCustomerEQ applies the EQ predicate on the "receives_customer" field. +func ReceivesCustomerEQ(v bool) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldReceivesCustomer, v)) +} + +// ReceivesCustomerNEQ applies the NEQ predicate on the "receives_customer" field. +func ReceivesCustomerNEQ(v bool) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNEQ(FieldReceivesCustomer, v)) +} + +// RemoteServicesEQ applies the EQ predicate on the "remote_services" field. +func RemoteServicesEQ(v bool) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldRemoteServices, v)) +} + +// RemoteServicesNEQ applies the NEQ predicate on the "remote_services" field. +func RemoteServicesNEQ(v bool) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNEQ(FieldRemoteServices, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// HasServiceLocalities applies the HasEdge predicate on the "service_localities" edge. +func HasServiceLocalities() predicate.ProviderProfile { + return predicate.ProviderProfile(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, ServiceLocalitiesTable, ServiceLocalitiesPrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasServiceLocalitiesWith applies the HasEdge predicate on the "service_localities" edge with a given conditions (other predicates). +func HasServiceLocalitiesWith(preds ...predicate.Locality) predicate.ProviderProfile { + return predicate.ProviderProfile(func(s *sql.Selector) { + step := newServiceLocalitiesStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasSpokenLanguages applies the HasEdge predicate on the "spoken_languages" edge. +func HasSpokenLanguages() predicate.ProviderProfile { + return predicate.ProviderProfile(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, SpokenLanguagesTable, SpokenLanguagesPrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasSpokenLanguagesWith applies the HasEdge predicate on the "spoken_languages" edge with a given conditions (other predicates). +func HasSpokenLanguagesWith(preds ...predicate.SpokenLanguage) predicate.ProviderProfile { + return predicate.ProviderProfile(func(s *sql.Selector) { + step := newSpokenLanguagesStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasServiceLocalityLinks applies the HasEdge predicate on the "service_locality_links" edge. +func HasServiceLocalityLinks() predicate.ProviderProfile { + return predicate.ProviderProfile(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, ServiceLocalityLinksTable, ServiceLocalityLinksColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasServiceLocalityLinksWith applies the HasEdge predicate on the "service_locality_links" edge with a given conditions (other predicates). +func HasServiceLocalityLinksWith(preds ...predicate.ProviderServiceLocality) predicate.ProviderProfile { + return predicate.ProviderProfile(func(s *sql.Selector) { + step := newServiceLocalityLinksStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasSpokenLanguageLinks applies the HasEdge predicate on the "spoken_language_links" edge. +func HasSpokenLanguageLinks() predicate.ProviderProfile { + return predicate.ProviderProfile(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, SpokenLanguageLinksTable, SpokenLanguageLinksColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasSpokenLanguageLinksWith applies the HasEdge predicate on the "spoken_language_links" edge with a given conditions (other predicates). +func HasSpokenLanguageLinksWith(preds ...predicate.ProviderSpokenLanguage) predicate.ProviderProfile { + return predicate.ProviderProfile(func(s *sql.Selector) { + step := newSpokenLanguageLinksStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.ProviderProfile) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.ProviderProfile) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.ProviderProfile) predicate.ProviderProfile { + return predicate.ProviderProfile(sql.NotPredicates(p)) +} diff --git a/backend/ent/providerprofile_create.go b/backend/ent/providerprofile_create.go new file mode 100644 index 0000000..eaa53cf --- /dev/null +++ b/backend/ent/providerprofile_create.go @@ -0,0 +1,463 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/google/uuid" +) + +// ProviderProfileCreate is the builder for creating a ProviderProfile entity. +type ProviderProfileCreate struct { + config + mutation *ProviderProfileMutation + hooks []Hook +} + +// SetDisplayName sets the "display_name" field. +func (_c *ProviderProfileCreate) SetDisplayName(v string) *ProviderProfileCreate { + _c.mutation.SetDisplayName(v) + return _c +} + +// SetProviderType sets the "provider_type" field. +func (_c *ProviderProfileCreate) SetProviderType(v string) *ProviderProfileCreate { + _c.mutation.SetProviderType(v) + return _c +} + +// SetBio sets the "bio" field. +func (_c *ProviderProfileCreate) SetBio(v string) *ProviderProfileCreate { + _c.mutation.SetBio(v) + return _c +} + +// SetPrimaryLocalityID sets the "primary_locality_id" field. +func (_c *ProviderProfileCreate) SetPrimaryLocalityID(v uuid.UUID) *ProviderProfileCreate { + _c.mutation.SetPrimaryLocalityID(v) + return _c +} + +// SetMaxTravelDistanceKm sets the "max_travel_distance_km" field. +func (_c *ProviderProfileCreate) SetMaxTravelDistanceKm(v int) *ProviderProfileCreate { + _c.mutation.SetMaxTravelDistanceKm(v) + return _c +} + +// SetTravelsToCustomer sets the "travels_to_customer" field. +func (_c *ProviderProfileCreate) SetTravelsToCustomer(v bool) *ProviderProfileCreate { + _c.mutation.SetTravelsToCustomer(v) + return _c +} + +// SetNillableTravelsToCustomer sets the "travels_to_customer" field if the given value is not nil. +func (_c *ProviderProfileCreate) SetNillableTravelsToCustomer(v *bool) *ProviderProfileCreate { + if v != nil { + _c.SetTravelsToCustomer(*v) + } + return _c +} + +// SetReceivesCustomer sets the "receives_customer" field. +func (_c *ProviderProfileCreate) SetReceivesCustomer(v bool) *ProviderProfileCreate { + _c.mutation.SetReceivesCustomer(v) + return _c +} + +// SetNillableReceivesCustomer sets the "receives_customer" field if the given value is not nil. +func (_c *ProviderProfileCreate) SetNillableReceivesCustomer(v *bool) *ProviderProfileCreate { + if v != nil { + _c.SetReceivesCustomer(*v) + } + return _c +} + +// SetRemoteServices sets the "remote_services" field. +func (_c *ProviderProfileCreate) SetRemoteServices(v bool) *ProviderProfileCreate { + _c.mutation.SetRemoteServices(v) + return _c +} + +// SetNillableRemoteServices sets the "remote_services" field if the given value is not nil. +func (_c *ProviderProfileCreate) SetNillableRemoteServices(v *bool) *ProviderProfileCreate { + if v != nil { + _c.SetRemoteServices(*v) + } + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *ProviderProfileCreate) SetCreatedAt(v time.Time) *ProviderProfileCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *ProviderProfileCreate) SetNillableCreatedAt(v *time.Time) *ProviderProfileCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *ProviderProfileCreate) SetUpdatedAt(v time.Time) *ProviderProfileCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (_c *ProviderProfileCreate) SetNillableUpdatedAt(v *time.Time) *ProviderProfileCreate { + if v != nil { + _c.SetUpdatedAt(*v) + } + return _c +} + +// SetID sets the "id" field. +func (_c *ProviderProfileCreate) SetID(v uuid.UUID) *ProviderProfileCreate { + _c.mutation.SetID(v) + return _c +} + +// AddServiceLocalityIDs adds the "service_localities" edge to the Locality entity by IDs. +func (_c *ProviderProfileCreate) AddServiceLocalityIDs(ids ...uuid.UUID) *ProviderProfileCreate { + _c.mutation.AddServiceLocalityIDs(ids...) + return _c +} + +// AddServiceLocalities adds the "service_localities" edges to the Locality entity. +func (_c *ProviderProfileCreate) AddServiceLocalities(v ...*Locality) *ProviderProfileCreate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddServiceLocalityIDs(ids...) +} + +// AddSpokenLanguageIDs adds the "spoken_languages" edge to the SpokenLanguage entity by IDs. +func (_c *ProviderProfileCreate) AddSpokenLanguageIDs(ids ...string) *ProviderProfileCreate { + _c.mutation.AddSpokenLanguageIDs(ids...) + return _c +} + +// AddSpokenLanguages adds the "spoken_languages" edges to the SpokenLanguage entity. +func (_c *ProviderProfileCreate) AddSpokenLanguages(v ...*SpokenLanguage) *ProviderProfileCreate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddSpokenLanguageIDs(ids...) +} + +// Mutation returns the ProviderProfileMutation object of the builder. +func (_c *ProviderProfileCreate) Mutation() *ProviderProfileMutation { + return _c.mutation +} + +// Save creates the ProviderProfile in the database. +func (_c *ProviderProfileCreate) Save(ctx context.Context) (*ProviderProfile, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *ProviderProfileCreate) SaveX(ctx context.Context) *ProviderProfile { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *ProviderProfileCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *ProviderProfileCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *ProviderProfileCreate) defaults() { + if _, ok := _c.mutation.TravelsToCustomer(); !ok { + v := providerprofile.DefaultTravelsToCustomer + _c.mutation.SetTravelsToCustomer(v) + } + if _, ok := _c.mutation.ReceivesCustomer(); !ok { + v := providerprofile.DefaultReceivesCustomer + _c.mutation.SetReceivesCustomer(v) + } + if _, ok := _c.mutation.RemoteServices(); !ok { + v := providerprofile.DefaultRemoteServices + _c.mutation.SetRemoteServices(v) + } + if _, ok := _c.mutation.CreatedAt(); !ok { + v := providerprofile.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + v := providerprofile.DefaultUpdatedAt() + _c.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *ProviderProfileCreate) check() error { + if _, ok := _c.mutation.DisplayName(); !ok { + return &ValidationError{Name: "display_name", err: errors.New(`ent: missing required field "ProviderProfile.display_name"`)} + } + if v, ok := _c.mutation.DisplayName(); ok { + if err := providerprofile.DisplayNameValidator(v); err != nil { + return &ValidationError{Name: "display_name", err: fmt.Errorf(`ent: validator failed for field "ProviderProfile.display_name": %w`, err)} + } + } + if _, ok := _c.mutation.ProviderType(); !ok { + return &ValidationError{Name: "provider_type", err: errors.New(`ent: missing required field "ProviderProfile.provider_type"`)} + } + if v, ok := _c.mutation.ProviderType(); ok { + if err := providerprofile.ProviderTypeValidator(v); err != nil { + return &ValidationError{Name: "provider_type", err: fmt.Errorf(`ent: validator failed for field "ProviderProfile.provider_type": %w`, err)} + } + } + if _, ok := _c.mutation.Bio(); !ok { + return &ValidationError{Name: "bio", err: errors.New(`ent: missing required field "ProviderProfile.bio"`)} + } + if v, ok := _c.mutation.Bio(); ok { + if err := providerprofile.BioValidator(v); err != nil { + return &ValidationError{Name: "bio", err: fmt.Errorf(`ent: validator failed for field "ProviderProfile.bio": %w`, err)} + } + } + if _, ok := _c.mutation.PrimaryLocalityID(); !ok { + return &ValidationError{Name: "primary_locality_id", err: errors.New(`ent: missing required field "ProviderProfile.primary_locality_id"`)} + } + if _, ok := _c.mutation.MaxTravelDistanceKm(); !ok { + return &ValidationError{Name: "max_travel_distance_km", err: errors.New(`ent: missing required field "ProviderProfile.max_travel_distance_km"`)} + } + if v, ok := _c.mutation.MaxTravelDistanceKm(); ok { + if err := providerprofile.MaxTravelDistanceKmValidator(v); err != nil { + return &ValidationError{Name: "max_travel_distance_km", err: fmt.Errorf(`ent: validator failed for field "ProviderProfile.max_travel_distance_km": %w`, err)} + } + } + if _, ok := _c.mutation.TravelsToCustomer(); !ok { + return &ValidationError{Name: "travels_to_customer", err: errors.New(`ent: missing required field "ProviderProfile.travels_to_customer"`)} + } + if _, ok := _c.mutation.ReceivesCustomer(); !ok { + return &ValidationError{Name: "receives_customer", err: errors.New(`ent: missing required field "ProviderProfile.receives_customer"`)} + } + if _, ok := _c.mutation.RemoteServices(); !ok { + return &ValidationError{Name: "remote_services", err: errors.New(`ent: missing required field "ProviderProfile.remote_services"`)} + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "ProviderProfile.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "ProviderProfile.updated_at"`)} + } + return nil +} + +func (_c *ProviderProfileCreate) sqlSave(ctx context.Context) (*ProviderProfile, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(*uuid.UUID); ok { + _node.ID = *id + } else if err := _node.ID.Scan(_spec.ID.Value); err != nil { + return nil, err + } + } + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *ProviderProfileCreate) createSpec() (*ProviderProfile, *sqlgraph.CreateSpec) { + var ( + _node = &ProviderProfile{config: _c.config} + _spec = sqlgraph.NewCreateSpec(providerprofile.Table, sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID)) + ) + if id, ok := _c.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = &id + } + if value, ok := _c.mutation.DisplayName(); ok { + _spec.SetField(providerprofile.FieldDisplayName, field.TypeString, value) + _node.DisplayName = value + } + if value, ok := _c.mutation.ProviderType(); ok { + _spec.SetField(providerprofile.FieldProviderType, field.TypeString, value) + _node.ProviderType = value + } + if value, ok := _c.mutation.Bio(); ok { + _spec.SetField(providerprofile.FieldBio, field.TypeString, value) + _node.Bio = value + } + if value, ok := _c.mutation.PrimaryLocalityID(); ok { + _spec.SetField(providerprofile.FieldPrimaryLocalityID, field.TypeUUID, value) + _node.PrimaryLocalityID = value + } + if value, ok := _c.mutation.MaxTravelDistanceKm(); ok { + _spec.SetField(providerprofile.FieldMaxTravelDistanceKm, field.TypeInt, value) + _node.MaxTravelDistanceKm = value + } + if value, ok := _c.mutation.TravelsToCustomer(); ok { + _spec.SetField(providerprofile.FieldTravelsToCustomer, field.TypeBool, value) + _node.TravelsToCustomer = value + } + if value, ok := _c.mutation.ReceivesCustomer(); ok { + _spec.SetField(providerprofile.FieldReceivesCustomer, field.TypeBool, value) + _node.ReceivesCustomer = value + } + if value, ok := _c.mutation.RemoteServices(); ok { + _spec.SetField(providerprofile.FieldRemoteServices, field.TypeBool, value) + _node.RemoteServices = value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(providerprofile.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(providerprofile.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if nodes := _c.mutation.ServiceLocalitiesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.ServiceLocalitiesTable, + Columns: providerprofile.ServiceLocalitiesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.SpokenLanguagesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.SpokenLanguagesTable, + Columns: providerprofile.SpokenLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// ProviderProfileCreateBulk is the builder for creating many ProviderProfile entities in bulk. +type ProviderProfileCreateBulk struct { + config + err error + builders []*ProviderProfileCreate +} + +// Save creates the ProviderProfile entities in the database. +func (_c *ProviderProfileCreateBulk) Save(ctx context.Context) ([]*ProviderProfile, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*ProviderProfile, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*ProviderProfileMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *ProviderProfileCreateBulk) SaveX(ctx context.Context) []*ProviderProfile { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *ProviderProfileCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *ProviderProfileCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/providerprofile_delete.go b/backend/ent/providerprofile_delete.go new file mode 100644 index 0000000..8253b58 --- /dev/null +++ b/backend/ent/providerprofile_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" +) + +// ProviderProfileDelete is the builder for deleting a ProviderProfile entity. +type ProviderProfileDelete struct { + config + hooks []Hook + mutation *ProviderProfileMutation +} + +// Where appends a list predicates to the ProviderProfileDelete builder. +func (_d *ProviderProfileDelete) Where(ps ...predicate.ProviderProfile) *ProviderProfileDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *ProviderProfileDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *ProviderProfileDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *ProviderProfileDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(providerprofile.Table, sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// ProviderProfileDeleteOne is the builder for deleting a single ProviderProfile entity. +type ProviderProfileDeleteOne struct { + _d *ProviderProfileDelete +} + +// Where appends a list predicates to the ProviderProfileDelete builder. +func (_d *ProviderProfileDeleteOne) Where(ps ...predicate.ProviderProfile) *ProviderProfileDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *ProviderProfileDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{providerprofile.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *ProviderProfileDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/providerprofile_query.go b/backend/ent/providerprofile_query.go new file mode 100644 index 0000000..ed3203a --- /dev/null +++ b/backend/ent/providerprofile_query.go @@ -0,0 +1,899 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "database/sql/driver" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerservicelocality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerspokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/google/uuid" +) + +// ProviderProfileQuery is the builder for querying ProviderProfile entities. +type ProviderProfileQuery struct { + config + ctx *QueryContext + order []providerprofile.OrderOption + inters []Interceptor + predicates []predicate.ProviderProfile + withServiceLocalities *LocalityQuery + withSpokenLanguages *SpokenLanguageQuery + withServiceLocalityLinks *ProviderServiceLocalityQuery + withSpokenLanguageLinks *ProviderSpokenLanguageQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the ProviderProfileQuery builder. +func (_q *ProviderProfileQuery) Where(ps ...predicate.ProviderProfile) *ProviderProfileQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *ProviderProfileQuery) Limit(limit int) *ProviderProfileQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *ProviderProfileQuery) Offset(offset int) *ProviderProfileQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *ProviderProfileQuery) Unique(unique bool) *ProviderProfileQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *ProviderProfileQuery) Order(o ...providerprofile.OrderOption) *ProviderProfileQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QueryServiceLocalities chains the current query on the "service_localities" edge. +func (_q *ProviderProfileQuery) QueryServiceLocalities() *LocalityQuery { + query := (&LocalityClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(providerprofile.Table, providerprofile.FieldID, selector), + sqlgraph.To(locality.Table, locality.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, providerprofile.ServiceLocalitiesTable, providerprofile.ServiceLocalitiesPrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QuerySpokenLanguages chains the current query on the "spoken_languages" edge. +func (_q *ProviderProfileQuery) QuerySpokenLanguages() *SpokenLanguageQuery { + query := (&SpokenLanguageClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(providerprofile.Table, providerprofile.FieldID, selector), + sqlgraph.To(spokenlanguage.Table, spokenlanguage.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, providerprofile.SpokenLanguagesTable, providerprofile.SpokenLanguagesPrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryServiceLocalityLinks chains the current query on the "service_locality_links" edge. +func (_q *ProviderProfileQuery) QueryServiceLocalityLinks() *ProviderServiceLocalityQuery { + query := (&ProviderServiceLocalityClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(providerprofile.Table, providerprofile.FieldID, selector), + sqlgraph.To(providerservicelocality.Table, providerservicelocality.ProfileColumn), + sqlgraph.Edge(sqlgraph.O2M, true, providerprofile.ServiceLocalityLinksTable, providerprofile.ServiceLocalityLinksColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QuerySpokenLanguageLinks chains the current query on the "spoken_language_links" edge. +func (_q *ProviderProfileQuery) QuerySpokenLanguageLinks() *ProviderSpokenLanguageQuery { + query := (&ProviderSpokenLanguageClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(providerprofile.Table, providerprofile.FieldID, selector), + sqlgraph.To(providerspokenlanguage.Table, providerspokenlanguage.ProfileColumn), + sqlgraph.Edge(sqlgraph.O2M, true, providerprofile.SpokenLanguageLinksTable, providerprofile.SpokenLanguageLinksColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first ProviderProfile entity from the query. +// Returns a *NotFoundError when no ProviderProfile was found. +func (_q *ProviderProfileQuery) First(ctx context.Context) (*ProviderProfile, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{providerprofile.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *ProviderProfileQuery) FirstX(ctx context.Context) *ProviderProfile { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first ProviderProfile ID from the query. +// Returns a *NotFoundError when no ProviderProfile ID was found. +func (_q *ProviderProfileQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{providerprofile.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *ProviderProfileQuery) FirstIDX(ctx context.Context) uuid.UUID { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single ProviderProfile entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one ProviderProfile entity is found. +// Returns a *NotFoundError when no ProviderProfile entities are found. +func (_q *ProviderProfileQuery) Only(ctx context.Context) (*ProviderProfile, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{providerprofile.Label} + default: + return nil, &NotSingularError{providerprofile.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *ProviderProfileQuery) OnlyX(ctx context.Context) *ProviderProfile { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only ProviderProfile ID in the query. +// Returns a *NotSingularError when more than one ProviderProfile ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *ProviderProfileQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{providerprofile.Label} + default: + err = &NotSingularError{providerprofile.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *ProviderProfileQuery) OnlyIDX(ctx context.Context) uuid.UUID { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of ProviderProfiles. +func (_q *ProviderProfileQuery) All(ctx context.Context) ([]*ProviderProfile, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*ProviderProfile, *ProviderProfileQuery]() + return withInterceptors[[]*ProviderProfile](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *ProviderProfileQuery) AllX(ctx context.Context) []*ProviderProfile { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of ProviderProfile IDs. +func (_q *ProviderProfileQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(providerprofile.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *ProviderProfileQuery) IDsX(ctx context.Context) []uuid.UUID { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *ProviderProfileQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*ProviderProfileQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *ProviderProfileQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *ProviderProfileQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *ProviderProfileQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the ProviderProfileQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *ProviderProfileQuery) Clone() *ProviderProfileQuery { + if _q == nil { + return nil + } + return &ProviderProfileQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]providerprofile.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.ProviderProfile{}, _q.predicates...), + withServiceLocalities: _q.withServiceLocalities.Clone(), + withSpokenLanguages: _q.withSpokenLanguages.Clone(), + withServiceLocalityLinks: _q.withServiceLocalityLinks.Clone(), + withSpokenLanguageLinks: _q.withSpokenLanguageLinks.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// WithServiceLocalities tells the query-builder to eager-load the nodes that are connected to +// the "service_localities" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ProviderProfileQuery) WithServiceLocalities(opts ...func(*LocalityQuery)) *ProviderProfileQuery { + query := (&LocalityClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withServiceLocalities = query + return _q +} + +// WithSpokenLanguages tells the query-builder to eager-load the nodes that are connected to +// the "spoken_languages" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ProviderProfileQuery) WithSpokenLanguages(opts ...func(*SpokenLanguageQuery)) *ProviderProfileQuery { + query := (&SpokenLanguageClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withSpokenLanguages = query + return _q +} + +// WithServiceLocalityLinks tells the query-builder to eager-load the nodes that are connected to +// the "service_locality_links" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ProviderProfileQuery) WithServiceLocalityLinks(opts ...func(*ProviderServiceLocalityQuery)) *ProviderProfileQuery { + query := (&ProviderServiceLocalityClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withServiceLocalityLinks = query + return _q +} + +// WithSpokenLanguageLinks tells the query-builder to eager-load the nodes that are connected to +// the "spoken_language_links" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ProviderProfileQuery) WithSpokenLanguageLinks(opts ...func(*ProviderSpokenLanguageQuery)) *ProviderProfileQuery { + query := (&ProviderSpokenLanguageClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withSpokenLanguageLinks = query + return _q +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// DisplayName string `json:"display_name,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.ProviderProfile.Query(). +// GroupBy(providerprofile.FieldDisplayName). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *ProviderProfileQuery) GroupBy(field string, fields ...string) *ProviderProfileGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &ProviderProfileGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = providerprofile.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// DisplayName string `json:"display_name,omitempty"` +// } +// +// client.ProviderProfile.Query(). +// Select(providerprofile.FieldDisplayName). +// Scan(ctx, &v) +func (_q *ProviderProfileQuery) Select(fields ...string) *ProviderProfileSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &ProviderProfileSelect{ProviderProfileQuery: _q} + sbuild.label = providerprofile.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a ProviderProfileSelect configured with the given aggregations. +func (_q *ProviderProfileQuery) Aggregate(fns ...AggregateFunc) *ProviderProfileSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *ProviderProfileQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !providerprofile.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *ProviderProfileQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ProviderProfile, error) { + var ( + nodes = []*ProviderProfile{} + _spec = _q.querySpec() + loadedTypes = [4]bool{ + _q.withServiceLocalities != nil, + _q.withSpokenLanguages != nil, + _q.withServiceLocalityLinks != nil, + _q.withSpokenLanguageLinks != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*ProviderProfile).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &ProviderProfile{config: _q.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := _q.withServiceLocalities; query != nil { + if err := _q.loadServiceLocalities(ctx, query, nodes, + func(n *ProviderProfile) { n.Edges.ServiceLocalities = []*Locality{} }, + func(n *ProviderProfile, e *Locality) { + n.Edges.ServiceLocalities = append(n.Edges.ServiceLocalities, e) + }); err != nil { + return nil, err + } + } + if query := _q.withSpokenLanguages; query != nil { + if err := _q.loadSpokenLanguages(ctx, query, nodes, + func(n *ProviderProfile) { n.Edges.SpokenLanguages = []*SpokenLanguage{} }, + func(n *ProviderProfile, e *SpokenLanguage) { + n.Edges.SpokenLanguages = append(n.Edges.SpokenLanguages, e) + }); err != nil { + return nil, err + } + } + if query := _q.withServiceLocalityLinks; query != nil { + if err := _q.loadServiceLocalityLinks(ctx, query, nodes, + func(n *ProviderProfile) { n.Edges.ServiceLocalityLinks = []*ProviderServiceLocality{} }, + func(n *ProviderProfile, e *ProviderServiceLocality) { + n.Edges.ServiceLocalityLinks = append(n.Edges.ServiceLocalityLinks, e) + }); err != nil { + return nil, err + } + } + if query := _q.withSpokenLanguageLinks; query != nil { + if err := _q.loadSpokenLanguageLinks(ctx, query, nodes, + func(n *ProviderProfile) { n.Edges.SpokenLanguageLinks = []*ProviderSpokenLanguage{} }, + func(n *ProviderProfile, e *ProviderSpokenLanguage) { + n.Edges.SpokenLanguageLinks = append(n.Edges.SpokenLanguageLinks, e) + }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *ProviderProfileQuery) loadServiceLocalities(ctx context.Context, query *LocalityQuery, nodes []*ProviderProfile, init func(*ProviderProfile), assign func(*ProviderProfile, *Locality)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[uuid.UUID]*ProviderProfile) + nids := make(map[uuid.UUID]map[*ProviderProfile]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(providerprofile.ServiceLocalitiesTable) + s.Join(joinT).On(s.C(locality.FieldID), joinT.C(providerprofile.ServiceLocalitiesPrimaryKey[1])) + s.Where(sql.InValues(joinT.C(providerprofile.ServiceLocalitiesPrimaryKey[0]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(providerprofile.ServiceLocalitiesPrimaryKey[0])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(uuid.UUID)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := *values[0].(*uuid.UUID) + inValue := *values[1].(*uuid.UUID) + if nids[inValue] == nil { + nids[inValue] = map[*ProviderProfile]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*Locality](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "service_localities" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} +func (_q *ProviderProfileQuery) loadSpokenLanguages(ctx context.Context, query *SpokenLanguageQuery, nodes []*ProviderProfile, init func(*ProviderProfile), assign func(*ProviderProfile, *SpokenLanguage)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[uuid.UUID]*ProviderProfile) + nids := make(map[string]map[*ProviderProfile]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(providerprofile.SpokenLanguagesTable) + s.Join(joinT).On(s.C(spokenlanguage.FieldID), joinT.C(providerprofile.SpokenLanguagesPrimaryKey[1])) + s.Where(sql.InValues(joinT.C(providerprofile.SpokenLanguagesPrimaryKey[0]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(providerprofile.SpokenLanguagesPrimaryKey[0])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(uuid.UUID)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := *values[0].(*uuid.UUID) + inValue := values[1].(*sql.NullString).String + if nids[inValue] == nil { + nids[inValue] = map[*ProviderProfile]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*SpokenLanguage](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "spoken_languages" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} +func (_q *ProviderProfileQuery) loadServiceLocalityLinks(ctx context.Context, query *ProviderServiceLocalityQuery, nodes []*ProviderProfile, init func(*ProviderProfile), assign func(*ProviderProfile, *ProviderServiceLocality)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[uuid.UUID]*ProviderProfile) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(providerservicelocality.FieldInternalUserID) + } + query.Where(predicate.ProviderServiceLocality(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(providerprofile.ServiceLocalityLinksColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.InternalUserID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "internal_user_id" returned %v for node %v`, fk, n) + } + assign(node, n) + } + return nil +} +func (_q *ProviderProfileQuery) loadSpokenLanguageLinks(ctx context.Context, query *ProviderSpokenLanguageQuery, nodes []*ProviderProfile, init func(*ProviderProfile), assign func(*ProviderProfile, *ProviderSpokenLanguage)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[uuid.UUID]*ProviderProfile) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(providerspokenlanguage.FieldInternalUserID) + } + query.Where(predicate.ProviderSpokenLanguage(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(providerprofile.SpokenLanguageLinksColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.InternalUserID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "internal_user_id" returned %v for node %v`, fk, n) + } + assign(node, n) + } + return nil +} + +func (_q *ProviderProfileQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *ProviderProfileQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(providerprofile.Table, providerprofile.Columns, sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, providerprofile.FieldID) + for i := range fields { + if fields[i] != providerprofile.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *ProviderProfileQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(providerprofile.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = providerprofile.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ProviderProfileGroupBy is the group-by builder for ProviderProfile entities. +type ProviderProfileGroupBy struct { + selector + build *ProviderProfileQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *ProviderProfileGroupBy) Aggregate(fns ...AggregateFunc) *ProviderProfileGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *ProviderProfileGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ProviderProfileQuery, *ProviderProfileGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *ProviderProfileGroupBy) sqlScan(ctx context.Context, root *ProviderProfileQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// ProviderProfileSelect is the builder for selecting fields of ProviderProfile entities. +type ProviderProfileSelect struct { + *ProviderProfileQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *ProviderProfileSelect) Aggregate(fns ...AggregateFunc) *ProviderProfileSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *ProviderProfileSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ProviderProfileQuery, *ProviderProfileSelect](ctx, _s.ProviderProfileQuery, _s, _s.inters, v) +} + +func (_s *ProviderProfileSelect) sqlScan(ctx context.Context, root *ProviderProfileQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/providerprofile_update.go b/backend/ent/providerprofile_update.go new file mode 100644 index 0000000..a842c31 --- /dev/null +++ b/backend/ent/providerprofile_update.go @@ -0,0 +1,887 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/google/uuid" +) + +// ProviderProfileUpdate is the builder for updating ProviderProfile entities. +type ProviderProfileUpdate struct { + config + hooks []Hook + mutation *ProviderProfileMutation +} + +// Where appends a list predicates to the ProviderProfileUpdate builder. +func (_u *ProviderProfileUpdate) Where(ps ...predicate.ProviderProfile) *ProviderProfileUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetDisplayName sets the "display_name" field. +func (_u *ProviderProfileUpdate) SetDisplayName(v string) *ProviderProfileUpdate { + _u.mutation.SetDisplayName(v) + return _u +} + +// SetNillableDisplayName sets the "display_name" field if the given value is not nil. +func (_u *ProviderProfileUpdate) SetNillableDisplayName(v *string) *ProviderProfileUpdate { + if v != nil { + _u.SetDisplayName(*v) + } + return _u +} + +// SetProviderType sets the "provider_type" field. +func (_u *ProviderProfileUpdate) SetProviderType(v string) *ProviderProfileUpdate { + _u.mutation.SetProviderType(v) + return _u +} + +// SetNillableProviderType sets the "provider_type" field if the given value is not nil. +func (_u *ProviderProfileUpdate) SetNillableProviderType(v *string) *ProviderProfileUpdate { + if v != nil { + _u.SetProviderType(*v) + } + return _u +} + +// SetBio sets the "bio" field. +func (_u *ProviderProfileUpdate) SetBio(v string) *ProviderProfileUpdate { + _u.mutation.SetBio(v) + return _u +} + +// SetNillableBio sets the "bio" field if the given value is not nil. +func (_u *ProviderProfileUpdate) SetNillableBio(v *string) *ProviderProfileUpdate { + if v != nil { + _u.SetBio(*v) + } + return _u +} + +// SetPrimaryLocalityID sets the "primary_locality_id" field. +func (_u *ProviderProfileUpdate) SetPrimaryLocalityID(v uuid.UUID) *ProviderProfileUpdate { + _u.mutation.SetPrimaryLocalityID(v) + return _u +} + +// SetNillablePrimaryLocalityID sets the "primary_locality_id" field if the given value is not nil. +func (_u *ProviderProfileUpdate) SetNillablePrimaryLocalityID(v *uuid.UUID) *ProviderProfileUpdate { + if v != nil { + _u.SetPrimaryLocalityID(*v) + } + return _u +} + +// SetMaxTravelDistanceKm sets the "max_travel_distance_km" field. +func (_u *ProviderProfileUpdate) SetMaxTravelDistanceKm(v int) *ProviderProfileUpdate { + _u.mutation.ResetMaxTravelDistanceKm() + _u.mutation.SetMaxTravelDistanceKm(v) + return _u +} + +// SetNillableMaxTravelDistanceKm sets the "max_travel_distance_km" field if the given value is not nil. +func (_u *ProviderProfileUpdate) SetNillableMaxTravelDistanceKm(v *int) *ProviderProfileUpdate { + if v != nil { + _u.SetMaxTravelDistanceKm(*v) + } + return _u +} + +// AddMaxTravelDistanceKm adds value to the "max_travel_distance_km" field. +func (_u *ProviderProfileUpdate) AddMaxTravelDistanceKm(v int) *ProviderProfileUpdate { + _u.mutation.AddMaxTravelDistanceKm(v) + return _u +} + +// SetTravelsToCustomer sets the "travels_to_customer" field. +func (_u *ProviderProfileUpdate) SetTravelsToCustomer(v bool) *ProviderProfileUpdate { + _u.mutation.SetTravelsToCustomer(v) + return _u +} + +// SetNillableTravelsToCustomer sets the "travels_to_customer" field if the given value is not nil. +func (_u *ProviderProfileUpdate) SetNillableTravelsToCustomer(v *bool) *ProviderProfileUpdate { + if v != nil { + _u.SetTravelsToCustomer(*v) + } + return _u +} + +// SetReceivesCustomer sets the "receives_customer" field. +func (_u *ProviderProfileUpdate) SetReceivesCustomer(v bool) *ProviderProfileUpdate { + _u.mutation.SetReceivesCustomer(v) + return _u +} + +// SetNillableReceivesCustomer sets the "receives_customer" field if the given value is not nil. +func (_u *ProviderProfileUpdate) SetNillableReceivesCustomer(v *bool) *ProviderProfileUpdate { + if v != nil { + _u.SetReceivesCustomer(*v) + } + return _u +} + +// SetRemoteServices sets the "remote_services" field. +func (_u *ProviderProfileUpdate) SetRemoteServices(v bool) *ProviderProfileUpdate { + _u.mutation.SetRemoteServices(v) + return _u +} + +// SetNillableRemoteServices sets the "remote_services" field if the given value is not nil. +func (_u *ProviderProfileUpdate) SetNillableRemoteServices(v *bool) *ProviderProfileUpdate { + if v != nil { + _u.SetRemoteServices(*v) + } + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *ProviderProfileUpdate) SetUpdatedAt(v time.Time) *ProviderProfileUpdate { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// AddServiceLocalityIDs adds the "service_localities" edge to the Locality entity by IDs. +func (_u *ProviderProfileUpdate) AddServiceLocalityIDs(ids ...uuid.UUID) *ProviderProfileUpdate { + _u.mutation.AddServiceLocalityIDs(ids...) + return _u +} + +// AddServiceLocalities adds the "service_localities" edges to the Locality entity. +func (_u *ProviderProfileUpdate) AddServiceLocalities(v ...*Locality) *ProviderProfileUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddServiceLocalityIDs(ids...) +} + +// AddSpokenLanguageIDs adds the "spoken_languages" edge to the SpokenLanguage entity by IDs. +func (_u *ProviderProfileUpdate) AddSpokenLanguageIDs(ids ...string) *ProviderProfileUpdate { + _u.mutation.AddSpokenLanguageIDs(ids...) + return _u +} + +// AddSpokenLanguages adds the "spoken_languages" edges to the SpokenLanguage entity. +func (_u *ProviderProfileUpdate) AddSpokenLanguages(v ...*SpokenLanguage) *ProviderProfileUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddSpokenLanguageIDs(ids...) +} + +// Mutation returns the ProviderProfileMutation object of the builder. +func (_u *ProviderProfileUpdate) Mutation() *ProviderProfileMutation { + return _u.mutation +} + +// ClearServiceLocalities clears all "service_localities" edges to the Locality entity. +func (_u *ProviderProfileUpdate) ClearServiceLocalities() *ProviderProfileUpdate { + _u.mutation.ClearServiceLocalities() + return _u +} + +// RemoveServiceLocalityIDs removes the "service_localities" edge to Locality entities by IDs. +func (_u *ProviderProfileUpdate) RemoveServiceLocalityIDs(ids ...uuid.UUID) *ProviderProfileUpdate { + _u.mutation.RemoveServiceLocalityIDs(ids...) + return _u +} + +// RemoveServiceLocalities removes "service_localities" edges to Locality entities. +func (_u *ProviderProfileUpdate) RemoveServiceLocalities(v ...*Locality) *ProviderProfileUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveServiceLocalityIDs(ids...) +} + +// ClearSpokenLanguages clears all "spoken_languages" edges to the SpokenLanguage entity. +func (_u *ProviderProfileUpdate) ClearSpokenLanguages() *ProviderProfileUpdate { + _u.mutation.ClearSpokenLanguages() + return _u +} + +// RemoveSpokenLanguageIDs removes the "spoken_languages" edge to SpokenLanguage entities by IDs. +func (_u *ProviderProfileUpdate) RemoveSpokenLanguageIDs(ids ...string) *ProviderProfileUpdate { + _u.mutation.RemoveSpokenLanguageIDs(ids...) + return _u +} + +// RemoveSpokenLanguages removes "spoken_languages" edges to SpokenLanguage entities. +func (_u *ProviderProfileUpdate) RemoveSpokenLanguages(v ...*SpokenLanguage) *ProviderProfileUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveSpokenLanguageIDs(ids...) +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *ProviderProfileUpdate) Save(ctx context.Context) (int, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *ProviderProfileUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *ProviderProfileUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *ProviderProfileUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *ProviderProfileUpdate) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := providerprofile.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *ProviderProfileUpdate) check() error { + if v, ok := _u.mutation.DisplayName(); ok { + if err := providerprofile.DisplayNameValidator(v); err != nil { + return &ValidationError{Name: "display_name", err: fmt.Errorf(`ent: validator failed for field "ProviderProfile.display_name": %w`, err)} + } + } + if v, ok := _u.mutation.ProviderType(); ok { + if err := providerprofile.ProviderTypeValidator(v); err != nil { + return &ValidationError{Name: "provider_type", err: fmt.Errorf(`ent: validator failed for field "ProviderProfile.provider_type": %w`, err)} + } + } + if v, ok := _u.mutation.Bio(); ok { + if err := providerprofile.BioValidator(v); err != nil { + return &ValidationError{Name: "bio", err: fmt.Errorf(`ent: validator failed for field "ProviderProfile.bio": %w`, err)} + } + } + if v, ok := _u.mutation.MaxTravelDistanceKm(); ok { + if err := providerprofile.MaxTravelDistanceKmValidator(v); err != nil { + return &ValidationError{Name: "max_travel_distance_km", err: fmt.Errorf(`ent: validator failed for field "ProviderProfile.max_travel_distance_km": %w`, err)} + } + } + return nil +} + +func (_u *ProviderProfileUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(providerprofile.Table, providerprofile.Columns, sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.DisplayName(); ok { + _spec.SetField(providerprofile.FieldDisplayName, field.TypeString, value) + } + if value, ok := _u.mutation.ProviderType(); ok { + _spec.SetField(providerprofile.FieldProviderType, field.TypeString, value) + } + if value, ok := _u.mutation.Bio(); ok { + _spec.SetField(providerprofile.FieldBio, field.TypeString, value) + } + if value, ok := _u.mutation.PrimaryLocalityID(); ok { + _spec.SetField(providerprofile.FieldPrimaryLocalityID, field.TypeUUID, value) + } + if value, ok := _u.mutation.MaxTravelDistanceKm(); ok { + _spec.SetField(providerprofile.FieldMaxTravelDistanceKm, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedMaxTravelDistanceKm(); ok { + _spec.AddField(providerprofile.FieldMaxTravelDistanceKm, field.TypeInt, value) + } + if value, ok := _u.mutation.TravelsToCustomer(); ok { + _spec.SetField(providerprofile.FieldTravelsToCustomer, field.TypeBool, value) + } + if value, ok := _u.mutation.ReceivesCustomer(); ok { + _spec.SetField(providerprofile.FieldReceivesCustomer, field.TypeBool, value) + } + if value, ok := _u.mutation.RemoteServices(); ok { + _spec.SetField(providerprofile.FieldRemoteServices, field.TypeBool, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(providerprofile.FieldUpdatedAt, field.TypeTime, value) + } + if _u.mutation.ServiceLocalitiesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.ServiceLocalitiesTable, + Columns: providerprofile.ServiceLocalitiesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedServiceLocalitiesIDs(); len(nodes) > 0 && !_u.mutation.ServiceLocalitiesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.ServiceLocalitiesTable, + Columns: providerprofile.ServiceLocalitiesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.ServiceLocalitiesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.ServiceLocalitiesTable, + Columns: providerprofile.ServiceLocalitiesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _u.mutation.SpokenLanguagesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.SpokenLanguagesTable, + Columns: providerprofile.SpokenLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedSpokenLanguagesIDs(); len(nodes) > 0 && !_u.mutation.SpokenLanguagesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.SpokenLanguagesTable, + Columns: providerprofile.SpokenLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.SpokenLanguagesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.SpokenLanguagesTable, + Columns: providerprofile.SpokenLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{providerprofile.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// ProviderProfileUpdateOne is the builder for updating a single ProviderProfile entity. +type ProviderProfileUpdateOne struct { + config + fields []string + hooks []Hook + mutation *ProviderProfileMutation +} + +// SetDisplayName sets the "display_name" field. +func (_u *ProviderProfileUpdateOne) SetDisplayName(v string) *ProviderProfileUpdateOne { + _u.mutation.SetDisplayName(v) + return _u +} + +// SetNillableDisplayName sets the "display_name" field if the given value is not nil. +func (_u *ProviderProfileUpdateOne) SetNillableDisplayName(v *string) *ProviderProfileUpdateOne { + if v != nil { + _u.SetDisplayName(*v) + } + return _u +} + +// SetProviderType sets the "provider_type" field. +func (_u *ProviderProfileUpdateOne) SetProviderType(v string) *ProviderProfileUpdateOne { + _u.mutation.SetProviderType(v) + return _u +} + +// SetNillableProviderType sets the "provider_type" field if the given value is not nil. +func (_u *ProviderProfileUpdateOne) SetNillableProviderType(v *string) *ProviderProfileUpdateOne { + if v != nil { + _u.SetProviderType(*v) + } + return _u +} + +// SetBio sets the "bio" field. +func (_u *ProviderProfileUpdateOne) SetBio(v string) *ProviderProfileUpdateOne { + _u.mutation.SetBio(v) + return _u +} + +// SetNillableBio sets the "bio" field if the given value is not nil. +func (_u *ProviderProfileUpdateOne) SetNillableBio(v *string) *ProviderProfileUpdateOne { + if v != nil { + _u.SetBio(*v) + } + return _u +} + +// SetPrimaryLocalityID sets the "primary_locality_id" field. +func (_u *ProviderProfileUpdateOne) SetPrimaryLocalityID(v uuid.UUID) *ProviderProfileUpdateOne { + _u.mutation.SetPrimaryLocalityID(v) + return _u +} + +// SetNillablePrimaryLocalityID sets the "primary_locality_id" field if the given value is not nil. +func (_u *ProviderProfileUpdateOne) SetNillablePrimaryLocalityID(v *uuid.UUID) *ProviderProfileUpdateOne { + if v != nil { + _u.SetPrimaryLocalityID(*v) + } + return _u +} + +// SetMaxTravelDistanceKm sets the "max_travel_distance_km" field. +func (_u *ProviderProfileUpdateOne) SetMaxTravelDistanceKm(v int) *ProviderProfileUpdateOne { + _u.mutation.ResetMaxTravelDistanceKm() + _u.mutation.SetMaxTravelDistanceKm(v) + return _u +} + +// SetNillableMaxTravelDistanceKm sets the "max_travel_distance_km" field if the given value is not nil. +func (_u *ProviderProfileUpdateOne) SetNillableMaxTravelDistanceKm(v *int) *ProviderProfileUpdateOne { + if v != nil { + _u.SetMaxTravelDistanceKm(*v) + } + return _u +} + +// AddMaxTravelDistanceKm adds value to the "max_travel_distance_km" field. +func (_u *ProviderProfileUpdateOne) AddMaxTravelDistanceKm(v int) *ProviderProfileUpdateOne { + _u.mutation.AddMaxTravelDistanceKm(v) + return _u +} + +// SetTravelsToCustomer sets the "travels_to_customer" field. +func (_u *ProviderProfileUpdateOne) SetTravelsToCustomer(v bool) *ProviderProfileUpdateOne { + _u.mutation.SetTravelsToCustomer(v) + return _u +} + +// SetNillableTravelsToCustomer sets the "travels_to_customer" field if the given value is not nil. +func (_u *ProviderProfileUpdateOne) SetNillableTravelsToCustomer(v *bool) *ProviderProfileUpdateOne { + if v != nil { + _u.SetTravelsToCustomer(*v) + } + return _u +} + +// SetReceivesCustomer sets the "receives_customer" field. +func (_u *ProviderProfileUpdateOne) SetReceivesCustomer(v bool) *ProviderProfileUpdateOne { + _u.mutation.SetReceivesCustomer(v) + return _u +} + +// SetNillableReceivesCustomer sets the "receives_customer" field if the given value is not nil. +func (_u *ProviderProfileUpdateOne) SetNillableReceivesCustomer(v *bool) *ProviderProfileUpdateOne { + if v != nil { + _u.SetReceivesCustomer(*v) + } + return _u +} + +// SetRemoteServices sets the "remote_services" field. +func (_u *ProviderProfileUpdateOne) SetRemoteServices(v bool) *ProviderProfileUpdateOne { + _u.mutation.SetRemoteServices(v) + return _u +} + +// SetNillableRemoteServices sets the "remote_services" field if the given value is not nil. +func (_u *ProviderProfileUpdateOne) SetNillableRemoteServices(v *bool) *ProviderProfileUpdateOne { + if v != nil { + _u.SetRemoteServices(*v) + } + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *ProviderProfileUpdateOne) SetUpdatedAt(v time.Time) *ProviderProfileUpdateOne { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// AddServiceLocalityIDs adds the "service_localities" edge to the Locality entity by IDs. +func (_u *ProviderProfileUpdateOne) AddServiceLocalityIDs(ids ...uuid.UUID) *ProviderProfileUpdateOne { + _u.mutation.AddServiceLocalityIDs(ids...) + return _u +} + +// AddServiceLocalities adds the "service_localities" edges to the Locality entity. +func (_u *ProviderProfileUpdateOne) AddServiceLocalities(v ...*Locality) *ProviderProfileUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddServiceLocalityIDs(ids...) +} + +// AddSpokenLanguageIDs adds the "spoken_languages" edge to the SpokenLanguage entity by IDs. +func (_u *ProviderProfileUpdateOne) AddSpokenLanguageIDs(ids ...string) *ProviderProfileUpdateOne { + _u.mutation.AddSpokenLanguageIDs(ids...) + return _u +} + +// AddSpokenLanguages adds the "spoken_languages" edges to the SpokenLanguage entity. +func (_u *ProviderProfileUpdateOne) AddSpokenLanguages(v ...*SpokenLanguage) *ProviderProfileUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddSpokenLanguageIDs(ids...) +} + +// Mutation returns the ProviderProfileMutation object of the builder. +func (_u *ProviderProfileUpdateOne) Mutation() *ProviderProfileMutation { + return _u.mutation +} + +// ClearServiceLocalities clears all "service_localities" edges to the Locality entity. +func (_u *ProviderProfileUpdateOne) ClearServiceLocalities() *ProviderProfileUpdateOne { + _u.mutation.ClearServiceLocalities() + return _u +} + +// RemoveServiceLocalityIDs removes the "service_localities" edge to Locality entities by IDs. +func (_u *ProviderProfileUpdateOne) RemoveServiceLocalityIDs(ids ...uuid.UUID) *ProviderProfileUpdateOne { + _u.mutation.RemoveServiceLocalityIDs(ids...) + return _u +} + +// RemoveServiceLocalities removes "service_localities" edges to Locality entities. +func (_u *ProviderProfileUpdateOne) RemoveServiceLocalities(v ...*Locality) *ProviderProfileUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveServiceLocalityIDs(ids...) +} + +// ClearSpokenLanguages clears all "spoken_languages" edges to the SpokenLanguage entity. +func (_u *ProviderProfileUpdateOne) ClearSpokenLanguages() *ProviderProfileUpdateOne { + _u.mutation.ClearSpokenLanguages() + return _u +} + +// RemoveSpokenLanguageIDs removes the "spoken_languages" edge to SpokenLanguage entities by IDs. +func (_u *ProviderProfileUpdateOne) RemoveSpokenLanguageIDs(ids ...string) *ProviderProfileUpdateOne { + _u.mutation.RemoveSpokenLanguageIDs(ids...) + return _u +} + +// RemoveSpokenLanguages removes "spoken_languages" edges to SpokenLanguage entities. +func (_u *ProviderProfileUpdateOne) RemoveSpokenLanguages(v ...*SpokenLanguage) *ProviderProfileUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveSpokenLanguageIDs(ids...) +} + +// Where appends a list predicates to the ProviderProfileUpdate builder. +func (_u *ProviderProfileUpdateOne) Where(ps ...predicate.ProviderProfile) *ProviderProfileUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *ProviderProfileUpdateOne) Select(field string, fields ...string) *ProviderProfileUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated ProviderProfile entity. +func (_u *ProviderProfileUpdateOne) Save(ctx context.Context) (*ProviderProfile, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *ProviderProfileUpdateOne) SaveX(ctx context.Context) *ProviderProfile { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *ProviderProfileUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *ProviderProfileUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *ProviderProfileUpdateOne) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := providerprofile.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *ProviderProfileUpdateOne) check() error { + if v, ok := _u.mutation.DisplayName(); ok { + if err := providerprofile.DisplayNameValidator(v); err != nil { + return &ValidationError{Name: "display_name", err: fmt.Errorf(`ent: validator failed for field "ProviderProfile.display_name": %w`, err)} + } + } + if v, ok := _u.mutation.ProviderType(); ok { + if err := providerprofile.ProviderTypeValidator(v); err != nil { + return &ValidationError{Name: "provider_type", err: fmt.Errorf(`ent: validator failed for field "ProviderProfile.provider_type": %w`, err)} + } + } + if v, ok := _u.mutation.Bio(); ok { + if err := providerprofile.BioValidator(v); err != nil { + return &ValidationError{Name: "bio", err: fmt.Errorf(`ent: validator failed for field "ProviderProfile.bio": %w`, err)} + } + } + if v, ok := _u.mutation.MaxTravelDistanceKm(); ok { + if err := providerprofile.MaxTravelDistanceKmValidator(v); err != nil { + return &ValidationError{Name: "max_travel_distance_km", err: fmt.Errorf(`ent: validator failed for field "ProviderProfile.max_travel_distance_km": %w`, err)} + } + } + return nil +} + +func (_u *ProviderProfileUpdateOne) sqlSave(ctx context.Context) (_node *ProviderProfile, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(providerprofile.Table, providerprofile.Columns, sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "ProviderProfile.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, providerprofile.FieldID) + for _, f := range fields { + if !providerprofile.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != providerprofile.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.DisplayName(); ok { + _spec.SetField(providerprofile.FieldDisplayName, field.TypeString, value) + } + if value, ok := _u.mutation.ProviderType(); ok { + _spec.SetField(providerprofile.FieldProviderType, field.TypeString, value) + } + if value, ok := _u.mutation.Bio(); ok { + _spec.SetField(providerprofile.FieldBio, field.TypeString, value) + } + if value, ok := _u.mutation.PrimaryLocalityID(); ok { + _spec.SetField(providerprofile.FieldPrimaryLocalityID, field.TypeUUID, value) + } + if value, ok := _u.mutation.MaxTravelDistanceKm(); ok { + _spec.SetField(providerprofile.FieldMaxTravelDistanceKm, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedMaxTravelDistanceKm(); ok { + _spec.AddField(providerprofile.FieldMaxTravelDistanceKm, field.TypeInt, value) + } + if value, ok := _u.mutation.TravelsToCustomer(); ok { + _spec.SetField(providerprofile.FieldTravelsToCustomer, field.TypeBool, value) + } + if value, ok := _u.mutation.ReceivesCustomer(); ok { + _spec.SetField(providerprofile.FieldReceivesCustomer, field.TypeBool, value) + } + if value, ok := _u.mutation.RemoteServices(); ok { + _spec.SetField(providerprofile.FieldRemoteServices, field.TypeBool, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(providerprofile.FieldUpdatedAt, field.TypeTime, value) + } + if _u.mutation.ServiceLocalitiesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.ServiceLocalitiesTable, + Columns: providerprofile.ServiceLocalitiesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedServiceLocalitiesIDs(); len(nodes) > 0 && !_u.mutation.ServiceLocalitiesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.ServiceLocalitiesTable, + Columns: providerprofile.ServiceLocalitiesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.ServiceLocalitiesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.ServiceLocalitiesTable, + Columns: providerprofile.ServiceLocalitiesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _u.mutation.SpokenLanguagesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.SpokenLanguagesTable, + Columns: providerprofile.SpokenLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedSpokenLanguagesIDs(); len(nodes) > 0 && !_u.mutation.SpokenLanguagesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.SpokenLanguagesTable, + Columns: providerprofile.SpokenLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.SpokenLanguagesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: providerprofile.SpokenLanguagesTable, + Columns: providerprofile.SpokenLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &ProviderProfile{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{providerprofile.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/ent/providerservicelocality.go b/backend/ent/providerservicelocality.go new file mode 100644 index 0000000..033a898 --- /dev/null +++ b/backend/ent/providerservicelocality.go @@ -0,0 +1,152 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerservicelocality" + "github.com/google/uuid" +) + +// ProviderServiceLocality is the model entity for the ProviderServiceLocality schema. +type ProviderServiceLocality struct { + config `json:"-"` + // InternalUserID holds the value of the "internal_user_id" field. + InternalUserID uuid.UUID `json:"internal_user_id,omitempty"` + // LocalityID holds the value of the "locality_id" field. + LocalityID uuid.UUID `json:"locality_id,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the ProviderServiceLocalityQuery when eager-loading is set. + Edges ProviderServiceLocalityEdges `json:"edges"` + selectValues sql.SelectValues +} + +// ProviderServiceLocalityEdges holds the relations/edges for other nodes in the graph. +type ProviderServiceLocalityEdges struct { + // Profile holds the value of the profile edge. + Profile *ProviderProfile `json:"profile,omitempty"` + // Locality holds the value of the locality edge. + Locality *Locality `json:"locality,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [2]bool +} + +// ProfileOrErr returns the Profile value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e ProviderServiceLocalityEdges) ProfileOrErr() (*ProviderProfile, error) { + if e.Profile != nil { + return e.Profile, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: providerprofile.Label} + } + return nil, &NotLoadedError{edge: "profile"} +} + +// LocalityOrErr returns the Locality value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e ProviderServiceLocalityEdges) LocalityOrErr() (*Locality, error) { + if e.Locality != nil { + return e.Locality, nil + } else if e.loadedTypes[1] { + return nil, &NotFoundError{label: locality.Label} + } + return nil, &NotLoadedError{edge: "locality"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*ProviderServiceLocality) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case providerservicelocality.FieldInternalUserID, providerservicelocality.FieldLocalityID: + values[i] = new(uuid.UUID) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the ProviderServiceLocality fields. +func (_m *ProviderServiceLocality) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case providerservicelocality.FieldInternalUserID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field internal_user_id", values[i]) + } else if value != nil { + _m.InternalUserID = *value + } + case providerservicelocality.FieldLocalityID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field locality_id", values[i]) + } else if value != nil { + _m.LocalityID = *value + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the ProviderServiceLocality. +// This includes values selected through modifiers, order, etc. +func (_m *ProviderServiceLocality) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QueryProfile queries the "profile" edge of the ProviderServiceLocality entity. +func (_m *ProviderServiceLocality) QueryProfile() *ProviderProfileQuery { + return NewProviderServiceLocalityClient(_m.config).QueryProfile(_m) +} + +// QueryLocality queries the "locality" edge of the ProviderServiceLocality entity. +func (_m *ProviderServiceLocality) QueryLocality() *LocalityQuery { + return NewProviderServiceLocalityClient(_m.config).QueryLocality(_m) +} + +// Update returns a builder for updating this ProviderServiceLocality. +// Note that you need to call ProviderServiceLocality.Unwrap() before calling this method if this ProviderServiceLocality +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *ProviderServiceLocality) Update() *ProviderServiceLocalityUpdateOne { + return NewProviderServiceLocalityClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the ProviderServiceLocality entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *ProviderServiceLocality) Unwrap() *ProviderServiceLocality { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: ProviderServiceLocality is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *ProviderServiceLocality) String() string { + var builder strings.Builder + builder.WriteString("ProviderServiceLocality(") + builder.WriteString("internal_user_id=") + builder.WriteString(fmt.Sprintf("%v", _m.InternalUserID)) + builder.WriteString(", ") + builder.WriteString("locality_id=") + builder.WriteString(fmt.Sprintf("%v", _m.LocalityID)) + builder.WriteByte(')') + return builder.String() +} + +// ProviderServiceLocalities is a parsable slice of ProviderServiceLocality. +type ProviderServiceLocalities []*ProviderServiceLocality diff --git a/backend/ent/providerservicelocality/providerservicelocality.go b/backend/ent/providerservicelocality/providerservicelocality.go new file mode 100644 index 0000000..aef9951 --- /dev/null +++ b/backend/ent/providerservicelocality/providerservicelocality.go @@ -0,0 +1,98 @@ +// Code generated by ent, DO NOT EDIT. + +package providerservicelocality + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the providerservicelocality type in the database. + Label = "provider_service_locality" + // FieldInternalUserID holds the string denoting the internal_user_id field in the database. + FieldInternalUserID = "internal_user_id" + // FieldLocalityID holds the string denoting the locality_id field in the database. + FieldLocalityID = "locality_id" + // EdgeProfile holds the string denoting the profile edge name in mutations. + EdgeProfile = "profile" + // EdgeLocality holds the string denoting the locality edge name in mutations. + EdgeLocality = "locality" + // ProviderProfileFieldID holds the string denoting the ID field of the ProviderProfile. + ProviderProfileFieldID = "internal_user_id" + // LocalityFieldID holds the string denoting the ID field of the Locality. + LocalityFieldID = "id" + // Table holds the table name of the providerservicelocality in the database. + Table = "provider_service_localities" + // ProfileTable is the table that holds the profile relation/edge. + ProfileTable = "provider_service_localities" + // ProfileInverseTable is the table name for the ProviderProfile entity. + // It exists in this package in order to avoid circular dependency with the "providerprofile" package. + ProfileInverseTable = "provider_profiles" + // ProfileColumn is the table column denoting the profile relation/edge. + ProfileColumn = "internal_user_id" + // LocalityTable is the table that holds the locality relation/edge. + LocalityTable = "provider_service_localities" + // LocalityInverseTable is the table name for the Locality entity. + // It exists in this package in order to avoid circular dependency with the "locality" package. + LocalityInverseTable = "localities" + // LocalityColumn is the table column denoting the locality relation/edge. + LocalityColumn = "locality_id" +) + +// Columns holds all SQL columns for providerservicelocality fields. +var Columns = []string{ + FieldInternalUserID, + FieldLocalityID, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +// OrderOption defines the ordering options for the ProviderServiceLocality queries. +type OrderOption func(*sql.Selector) + +// ByInternalUserID orders the results by the internal_user_id field. +func ByInternalUserID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldInternalUserID, opts...).ToFunc() +} + +// ByLocalityID orders the results by the locality_id field. +func ByLocalityID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLocalityID, opts...).ToFunc() +} + +// ByProfileField orders the results by profile field. +func ByProfileField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newProfileStep(), sql.OrderByField(field, opts...)) + } +} + +// ByLocalityField orders the results by locality field. +func ByLocalityField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newLocalityStep(), sql.OrderByField(field, opts...)) + } +} +func newProfileStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, ProfileColumn), + sqlgraph.To(ProfileInverseTable, ProviderProfileFieldID), + sqlgraph.Edge(sqlgraph.M2O, false, ProfileTable, ProfileColumn), + ) +} +func newLocalityStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, LocalityColumn), + sqlgraph.To(LocalityInverseTable, LocalityFieldID), + sqlgraph.Edge(sqlgraph.M2O, false, LocalityTable, LocalityColumn), + ) +} diff --git a/backend/ent/providerservicelocality/where.go b/backend/ent/providerservicelocality/where.go new file mode 100644 index 0000000..ae7505d --- /dev/null +++ b/backend/ent/providerservicelocality/where.go @@ -0,0 +1,121 @@ +// Code generated by ent, DO NOT EDIT. + +package providerservicelocality + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +// InternalUserID applies equality check predicate on the "internal_user_id" field. It's identical to InternalUserIDEQ. +func InternalUserID(v uuid.UUID) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.FieldEQ(FieldInternalUserID, v)) +} + +// LocalityID applies equality check predicate on the "locality_id" field. It's identical to LocalityIDEQ. +func LocalityID(v uuid.UUID) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.FieldEQ(FieldLocalityID, v)) +} + +// InternalUserIDEQ applies the EQ predicate on the "internal_user_id" field. +func InternalUserIDEQ(v uuid.UUID) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.FieldEQ(FieldInternalUserID, v)) +} + +// InternalUserIDNEQ applies the NEQ predicate on the "internal_user_id" field. +func InternalUserIDNEQ(v uuid.UUID) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.FieldNEQ(FieldInternalUserID, v)) +} + +// InternalUserIDIn applies the In predicate on the "internal_user_id" field. +func InternalUserIDIn(vs ...uuid.UUID) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.FieldIn(FieldInternalUserID, vs...)) +} + +// InternalUserIDNotIn applies the NotIn predicate on the "internal_user_id" field. +func InternalUserIDNotIn(vs ...uuid.UUID) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.FieldNotIn(FieldInternalUserID, vs...)) +} + +// LocalityIDEQ applies the EQ predicate on the "locality_id" field. +func LocalityIDEQ(v uuid.UUID) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.FieldEQ(FieldLocalityID, v)) +} + +// LocalityIDNEQ applies the NEQ predicate on the "locality_id" field. +func LocalityIDNEQ(v uuid.UUID) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.FieldNEQ(FieldLocalityID, v)) +} + +// LocalityIDIn applies the In predicate on the "locality_id" field. +func LocalityIDIn(vs ...uuid.UUID) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.FieldIn(FieldLocalityID, vs...)) +} + +// LocalityIDNotIn applies the NotIn predicate on the "locality_id" field. +func LocalityIDNotIn(vs ...uuid.UUID) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.FieldNotIn(FieldLocalityID, vs...)) +} + +// HasProfile applies the HasEdge predicate on the "profile" edge. +func HasProfile() predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, ProfileColumn), + sqlgraph.Edge(sqlgraph.M2O, false, ProfileTable, ProfileColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasProfileWith applies the HasEdge predicate on the "profile" edge with a given conditions (other predicates). +func HasProfileWith(preds ...predicate.ProviderProfile) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(func(s *sql.Selector) { + step := newProfileStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasLocality applies the HasEdge predicate on the "locality" edge. +func HasLocality() predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, LocalityColumn), + sqlgraph.Edge(sqlgraph.M2O, false, LocalityTable, LocalityColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasLocalityWith applies the HasEdge predicate on the "locality" edge with a given conditions (other predicates). +func HasLocalityWith(preds ...predicate.Locality) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(func(s *sql.Selector) { + step := newLocalityStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.ProviderServiceLocality) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.ProviderServiceLocality) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.ProviderServiceLocality) predicate.ProviderServiceLocality { + return predicate.ProviderServiceLocality(sql.NotPredicates(p)) +} diff --git a/backend/ent/providerservicelocality_create.go b/backend/ent/providerservicelocality_create.go new file mode 100644 index 0000000..02d418f --- /dev/null +++ b/backend/ent/providerservicelocality_create.go @@ -0,0 +1,238 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerservicelocality" + "github.com/google/uuid" +) + +// ProviderServiceLocalityCreate is the builder for creating a ProviderServiceLocality entity. +type ProviderServiceLocalityCreate struct { + config + mutation *ProviderServiceLocalityMutation + hooks []Hook +} + +// SetInternalUserID sets the "internal_user_id" field. +func (_c *ProviderServiceLocalityCreate) SetInternalUserID(v uuid.UUID) *ProviderServiceLocalityCreate { + _c.mutation.SetInternalUserID(v) + return _c +} + +// SetLocalityID sets the "locality_id" field. +func (_c *ProviderServiceLocalityCreate) SetLocalityID(v uuid.UUID) *ProviderServiceLocalityCreate { + _c.mutation.SetLocalityID(v) + return _c +} + +// SetProfileID sets the "profile" edge to the ProviderProfile entity by ID. +func (_c *ProviderServiceLocalityCreate) SetProfileID(id uuid.UUID) *ProviderServiceLocalityCreate { + _c.mutation.SetProfileID(id) + return _c +} + +// SetProfile sets the "profile" edge to the ProviderProfile entity. +func (_c *ProviderServiceLocalityCreate) SetProfile(v *ProviderProfile) *ProviderServiceLocalityCreate { + return _c.SetProfileID(v.ID) +} + +// SetLocality sets the "locality" edge to the Locality entity. +func (_c *ProviderServiceLocalityCreate) SetLocality(v *Locality) *ProviderServiceLocalityCreate { + return _c.SetLocalityID(v.ID) +} + +// Mutation returns the ProviderServiceLocalityMutation object of the builder. +func (_c *ProviderServiceLocalityCreate) Mutation() *ProviderServiceLocalityMutation { + return _c.mutation +} + +// Save creates the ProviderServiceLocality in the database. +func (_c *ProviderServiceLocalityCreate) Save(ctx context.Context) (*ProviderServiceLocality, error) { + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *ProviderServiceLocalityCreate) SaveX(ctx context.Context) *ProviderServiceLocality { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *ProviderServiceLocalityCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *ProviderServiceLocalityCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *ProviderServiceLocalityCreate) check() error { + if _, ok := _c.mutation.InternalUserID(); !ok { + return &ValidationError{Name: "internal_user_id", err: errors.New(`ent: missing required field "ProviderServiceLocality.internal_user_id"`)} + } + if _, ok := _c.mutation.LocalityID(); !ok { + return &ValidationError{Name: "locality_id", err: errors.New(`ent: missing required field "ProviderServiceLocality.locality_id"`)} + } + if len(_c.mutation.ProfileIDs()) == 0 { + return &ValidationError{Name: "profile", err: errors.New(`ent: missing required edge "ProviderServiceLocality.profile"`)} + } + if len(_c.mutation.LocalityIDs()) == 0 { + return &ValidationError{Name: "locality", err: errors.New(`ent: missing required edge "ProviderServiceLocality.locality"`)} + } + return nil +} + +func (_c *ProviderServiceLocalityCreate) sqlSave(ctx context.Context) (*ProviderServiceLocality, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + return _node, nil +} + +func (_c *ProviderServiceLocalityCreate) createSpec() (*ProviderServiceLocality, *sqlgraph.CreateSpec) { + var ( + _node = &ProviderServiceLocality{config: _c.config} + _spec = sqlgraph.NewCreateSpec(providerservicelocality.Table, nil) + ) + if nodes := _c.mutation.ProfileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: providerservicelocality.ProfileTable, + Columns: []string{providerservicelocality.ProfileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.InternalUserID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.LocalityIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: providerservicelocality.LocalityTable, + Columns: []string{providerservicelocality.LocalityColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(locality.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.LocalityID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// ProviderServiceLocalityCreateBulk is the builder for creating many ProviderServiceLocality entities in bulk. +type ProviderServiceLocalityCreateBulk struct { + config + err error + builders []*ProviderServiceLocalityCreate +} + +// Save creates the ProviderServiceLocality entities in the database. +func (_c *ProviderServiceLocalityCreateBulk) Save(ctx context.Context) ([]*ProviderServiceLocality, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*ProviderServiceLocality, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*ProviderServiceLocalityMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *ProviderServiceLocalityCreateBulk) SaveX(ctx context.Context) []*ProviderServiceLocality { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *ProviderServiceLocalityCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *ProviderServiceLocalityCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/providerservicelocality_delete.go b/backend/ent/providerservicelocality_delete.go new file mode 100644 index 0000000..640f235 --- /dev/null +++ b/backend/ent/providerservicelocality_delete.go @@ -0,0 +1,87 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerservicelocality" +) + +// ProviderServiceLocalityDelete is the builder for deleting a ProviderServiceLocality entity. +type ProviderServiceLocalityDelete struct { + config + hooks []Hook + mutation *ProviderServiceLocalityMutation +} + +// Where appends a list predicates to the ProviderServiceLocalityDelete builder. +func (_d *ProviderServiceLocalityDelete) Where(ps ...predicate.ProviderServiceLocality) *ProviderServiceLocalityDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *ProviderServiceLocalityDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *ProviderServiceLocalityDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *ProviderServiceLocalityDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(providerservicelocality.Table, nil) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// ProviderServiceLocalityDeleteOne is the builder for deleting a single ProviderServiceLocality entity. +type ProviderServiceLocalityDeleteOne struct { + _d *ProviderServiceLocalityDelete +} + +// Where appends a list predicates to the ProviderServiceLocalityDelete builder. +func (_d *ProviderServiceLocalityDeleteOne) Where(ps ...predicate.ProviderServiceLocality) *ProviderServiceLocalityDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *ProviderServiceLocalityDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{providerservicelocality.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *ProviderServiceLocalityDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/providerservicelocality_query.go b/backend/ent/providerservicelocality_query.go new file mode 100644 index 0000000..7f6c0ef --- /dev/null +++ b/backend/ent/providerservicelocality_query.go @@ -0,0 +1,604 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerservicelocality" + "github.com/google/uuid" +) + +// ProviderServiceLocalityQuery is the builder for querying ProviderServiceLocality entities. +type ProviderServiceLocalityQuery struct { + config + ctx *QueryContext + order []providerservicelocality.OrderOption + inters []Interceptor + predicates []predicate.ProviderServiceLocality + withProfile *ProviderProfileQuery + withLocality *LocalityQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the ProviderServiceLocalityQuery builder. +func (_q *ProviderServiceLocalityQuery) Where(ps ...predicate.ProviderServiceLocality) *ProviderServiceLocalityQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *ProviderServiceLocalityQuery) Limit(limit int) *ProviderServiceLocalityQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *ProviderServiceLocalityQuery) Offset(offset int) *ProviderServiceLocalityQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *ProviderServiceLocalityQuery) Unique(unique bool) *ProviderServiceLocalityQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *ProviderServiceLocalityQuery) Order(o ...providerservicelocality.OrderOption) *ProviderServiceLocalityQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QueryProfile chains the current query on the "profile" edge. +func (_q *ProviderServiceLocalityQuery) QueryProfile() *ProviderProfileQuery { + query := (&ProviderProfileClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(providerservicelocality.Table, providerservicelocality.ProfileColumn, selector), + sqlgraph.To(providerprofile.Table, providerprofile.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, providerservicelocality.ProfileTable, providerservicelocality.ProfileColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryLocality chains the current query on the "locality" edge. +func (_q *ProviderServiceLocalityQuery) QueryLocality() *LocalityQuery { + query := (&LocalityClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(providerservicelocality.Table, providerservicelocality.LocalityColumn, selector), + sqlgraph.To(locality.Table, locality.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, providerservicelocality.LocalityTable, providerservicelocality.LocalityColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first ProviderServiceLocality entity from the query. +// Returns a *NotFoundError when no ProviderServiceLocality was found. +func (_q *ProviderServiceLocalityQuery) First(ctx context.Context) (*ProviderServiceLocality, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{providerservicelocality.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *ProviderServiceLocalityQuery) FirstX(ctx context.Context) *ProviderServiceLocality { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// Only returns a single ProviderServiceLocality entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one ProviderServiceLocality entity is found. +// Returns a *NotFoundError when no ProviderServiceLocality entities are found. +func (_q *ProviderServiceLocalityQuery) Only(ctx context.Context) (*ProviderServiceLocality, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{providerservicelocality.Label} + default: + return nil, &NotSingularError{providerservicelocality.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *ProviderServiceLocalityQuery) OnlyX(ctx context.Context) *ProviderServiceLocality { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// All executes the query and returns a list of ProviderServiceLocalities. +func (_q *ProviderServiceLocalityQuery) All(ctx context.Context) ([]*ProviderServiceLocality, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*ProviderServiceLocality, *ProviderServiceLocalityQuery]() + return withInterceptors[[]*ProviderServiceLocality](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *ProviderServiceLocalityQuery) AllX(ctx context.Context) []*ProviderServiceLocality { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// Count returns the count of the given query. +func (_q *ProviderServiceLocalityQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*ProviderServiceLocalityQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *ProviderServiceLocalityQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *ProviderServiceLocalityQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.First(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *ProviderServiceLocalityQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the ProviderServiceLocalityQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *ProviderServiceLocalityQuery) Clone() *ProviderServiceLocalityQuery { + if _q == nil { + return nil + } + return &ProviderServiceLocalityQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]providerservicelocality.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.ProviderServiceLocality{}, _q.predicates...), + withProfile: _q.withProfile.Clone(), + withLocality: _q.withLocality.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// WithProfile tells the query-builder to eager-load the nodes that are connected to +// the "profile" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ProviderServiceLocalityQuery) WithProfile(opts ...func(*ProviderProfileQuery)) *ProviderServiceLocalityQuery { + query := (&ProviderProfileClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withProfile = query + return _q +} + +// WithLocality tells the query-builder to eager-load the nodes that are connected to +// the "locality" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ProviderServiceLocalityQuery) WithLocality(opts ...func(*LocalityQuery)) *ProviderServiceLocalityQuery { + query := (&LocalityClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withLocality = query + return _q +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// InternalUserID uuid.UUID `json:"internal_user_id,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.ProviderServiceLocality.Query(). +// GroupBy(providerservicelocality.FieldInternalUserID). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *ProviderServiceLocalityQuery) GroupBy(field string, fields ...string) *ProviderServiceLocalityGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &ProviderServiceLocalityGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = providerservicelocality.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// InternalUserID uuid.UUID `json:"internal_user_id,omitempty"` +// } +// +// client.ProviderServiceLocality.Query(). +// Select(providerservicelocality.FieldInternalUserID). +// Scan(ctx, &v) +func (_q *ProviderServiceLocalityQuery) Select(fields ...string) *ProviderServiceLocalitySelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &ProviderServiceLocalitySelect{ProviderServiceLocalityQuery: _q} + sbuild.label = providerservicelocality.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a ProviderServiceLocalitySelect configured with the given aggregations. +func (_q *ProviderServiceLocalityQuery) Aggregate(fns ...AggregateFunc) *ProviderServiceLocalitySelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *ProviderServiceLocalityQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !providerservicelocality.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *ProviderServiceLocalityQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ProviderServiceLocality, error) { + var ( + nodes = []*ProviderServiceLocality{} + _spec = _q.querySpec() + loadedTypes = [2]bool{ + _q.withProfile != nil, + _q.withLocality != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*ProviderServiceLocality).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &ProviderServiceLocality{config: _q.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := _q.withProfile; query != nil { + if err := _q.loadProfile(ctx, query, nodes, nil, + func(n *ProviderServiceLocality, e *ProviderProfile) { n.Edges.Profile = e }); err != nil { + return nil, err + } + } + if query := _q.withLocality; query != nil { + if err := _q.loadLocality(ctx, query, nodes, nil, + func(n *ProviderServiceLocality, e *Locality) { n.Edges.Locality = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *ProviderServiceLocalityQuery) loadProfile(ctx context.Context, query *ProviderProfileQuery, nodes []*ProviderServiceLocality, init func(*ProviderServiceLocality), assign func(*ProviderServiceLocality, *ProviderProfile)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*ProviderServiceLocality) + for i := range nodes { + fk := nodes[i].InternalUserID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(providerprofile.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "internal_user_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (_q *ProviderServiceLocalityQuery) loadLocality(ctx context.Context, query *LocalityQuery, nodes []*ProviderServiceLocality, init func(*ProviderServiceLocality), assign func(*ProviderServiceLocality, *Locality)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*ProviderServiceLocality) + for i := range nodes { + fk := nodes[i].LocalityID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(locality.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "locality_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (_q *ProviderServiceLocalityQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Unique = false + _spec.Node.Columns = nil + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *ProviderServiceLocalityQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(providerservicelocality.Table, providerservicelocality.Columns, nil) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + for i := range fields { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + if _q.withProfile != nil { + _spec.Node.AddColumnOnce(providerservicelocality.FieldInternalUserID) + } + if _q.withLocality != nil { + _spec.Node.AddColumnOnce(providerservicelocality.FieldLocalityID) + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *ProviderServiceLocalityQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(providerservicelocality.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = providerservicelocality.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ProviderServiceLocalityGroupBy is the group-by builder for ProviderServiceLocality entities. +type ProviderServiceLocalityGroupBy struct { + selector + build *ProviderServiceLocalityQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *ProviderServiceLocalityGroupBy) Aggregate(fns ...AggregateFunc) *ProviderServiceLocalityGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *ProviderServiceLocalityGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ProviderServiceLocalityQuery, *ProviderServiceLocalityGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *ProviderServiceLocalityGroupBy) sqlScan(ctx context.Context, root *ProviderServiceLocalityQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// ProviderServiceLocalitySelect is the builder for selecting fields of ProviderServiceLocality entities. +type ProviderServiceLocalitySelect struct { + *ProviderServiceLocalityQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *ProviderServiceLocalitySelect) Aggregate(fns ...AggregateFunc) *ProviderServiceLocalitySelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *ProviderServiceLocalitySelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ProviderServiceLocalityQuery, *ProviderServiceLocalitySelect](ctx, _s.ProviderServiceLocalityQuery, _s, _s.inters, v) +} + +func (_s *ProviderServiceLocalitySelect) sqlScan(ctx context.Context, root *ProviderServiceLocalityQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/providerservicelocality_update.go b/backend/ent/providerservicelocality_update.go new file mode 100644 index 0000000..c7ad96e --- /dev/null +++ b/backend/ent/providerservicelocality_update.go @@ -0,0 +1,205 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerservicelocality" +) + +// ProviderServiceLocalityUpdate is the builder for updating ProviderServiceLocality entities. +type ProviderServiceLocalityUpdate struct { + config + hooks []Hook + mutation *ProviderServiceLocalityMutation +} + +// Where appends a list predicates to the ProviderServiceLocalityUpdate builder. +func (_u *ProviderServiceLocalityUpdate) Where(ps ...predicate.ProviderServiceLocality) *ProviderServiceLocalityUpdate { + _u.mutation.Where(ps...) + return _u +} + +// Mutation returns the ProviderServiceLocalityMutation object of the builder. +func (_u *ProviderServiceLocalityUpdate) Mutation() *ProviderServiceLocalityMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *ProviderServiceLocalityUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *ProviderServiceLocalityUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *ProviderServiceLocalityUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *ProviderServiceLocalityUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *ProviderServiceLocalityUpdate) check() error { + if _u.mutation.ProfileCleared() && len(_u.mutation.ProfileIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "ProviderServiceLocality.profile"`) + } + if _u.mutation.LocalityCleared() && len(_u.mutation.LocalityIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "ProviderServiceLocality.locality"`) + } + return nil +} + +func (_u *ProviderServiceLocalityUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(providerservicelocality.Table, providerservicelocality.Columns, sqlgraph.NewFieldSpec(providerservicelocality.FieldInternalUserID, field.TypeUUID), sqlgraph.NewFieldSpec(providerservicelocality.FieldLocalityID, field.TypeUUID)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{providerservicelocality.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// ProviderServiceLocalityUpdateOne is the builder for updating a single ProviderServiceLocality entity. +type ProviderServiceLocalityUpdateOne struct { + config + fields []string + hooks []Hook + mutation *ProviderServiceLocalityMutation +} + +// Mutation returns the ProviderServiceLocalityMutation object of the builder. +func (_u *ProviderServiceLocalityUpdateOne) Mutation() *ProviderServiceLocalityMutation { + return _u.mutation +} + +// Where appends a list predicates to the ProviderServiceLocalityUpdate builder. +func (_u *ProviderServiceLocalityUpdateOne) Where(ps ...predicate.ProviderServiceLocality) *ProviderServiceLocalityUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *ProviderServiceLocalityUpdateOne) Select(field string, fields ...string) *ProviderServiceLocalityUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated ProviderServiceLocality entity. +func (_u *ProviderServiceLocalityUpdateOne) Save(ctx context.Context) (*ProviderServiceLocality, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *ProviderServiceLocalityUpdateOne) SaveX(ctx context.Context) *ProviderServiceLocality { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *ProviderServiceLocalityUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *ProviderServiceLocalityUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *ProviderServiceLocalityUpdateOne) check() error { + if _u.mutation.ProfileCleared() && len(_u.mutation.ProfileIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "ProviderServiceLocality.profile"`) + } + if _u.mutation.LocalityCleared() && len(_u.mutation.LocalityIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "ProviderServiceLocality.locality"`) + } + return nil +} + +func (_u *ProviderServiceLocalityUpdateOne) sqlSave(ctx context.Context) (_node *ProviderServiceLocality, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(providerservicelocality.Table, providerservicelocality.Columns, sqlgraph.NewFieldSpec(providerservicelocality.FieldInternalUserID, field.TypeUUID), sqlgraph.NewFieldSpec(providerservicelocality.FieldLocalityID, field.TypeUUID)) + if id, ok := _u.mutation.InternalUserID(); !ok { + return nil, &ValidationError{Name: "internal_user_id", err: errors.New(`ent: missing "ProviderServiceLocality.internal_user_id" for update`)} + } else { + _spec.Node.CompositeID[0].Value = id + } + if id, ok := _u.mutation.LocalityID(); !ok { + return nil, &ValidationError{Name: "locality_id", err: errors.New(`ent: missing "ProviderServiceLocality.locality_id" for update`)} + } else { + _spec.Node.CompositeID[1].Value = id + } + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, len(fields)) + for i, f := range fields { + if !providerservicelocality.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + _spec.Node.Columns[i] = f + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + _node = &ProviderServiceLocality{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{providerservicelocality.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/ent/providerspokenlanguage.go b/backend/ent/providerspokenlanguage.go new file mode 100644 index 0000000..f327d43 --- /dev/null +++ b/backend/ent/providerspokenlanguage.go @@ -0,0 +1,154 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerspokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/google/uuid" +) + +// ProviderSpokenLanguage is the model entity for the ProviderSpokenLanguage schema. +type ProviderSpokenLanguage struct { + config `json:"-"` + // InternalUserID holds the value of the "internal_user_id" field. + InternalUserID uuid.UUID `json:"internal_user_id,omitempty"` + // LanguageCode holds the value of the "language_code" field. + LanguageCode string `json:"language_code,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the ProviderSpokenLanguageQuery when eager-loading is set. + Edges ProviderSpokenLanguageEdges `json:"edges"` + selectValues sql.SelectValues +} + +// ProviderSpokenLanguageEdges holds the relations/edges for other nodes in the graph. +type ProviderSpokenLanguageEdges struct { + // Profile holds the value of the profile edge. + Profile *ProviderProfile `json:"profile,omitempty"` + // Language holds the value of the language edge. + Language *SpokenLanguage `json:"language,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [2]bool +} + +// ProfileOrErr returns the Profile value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e ProviderSpokenLanguageEdges) ProfileOrErr() (*ProviderProfile, error) { + if e.Profile != nil { + return e.Profile, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: providerprofile.Label} + } + return nil, &NotLoadedError{edge: "profile"} +} + +// LanguageOrErr returns the Language value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e ProviderSpokenLanguageEdges) LanguageOrErr() (*SpokenLanguage, error) { + if e.Language != nil { + return e.Language, nil + } else if e.loadedTypes[1] { + return nil, &NotFoundError{label: spokenlanguage.Label} + } + return nil, &NotLoadedError{edge: "language"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*ProviderSpokenLanguage) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case providerspokenlanguage.FieldLanguageCode: + values[i] = new(sql.NullString) + case providerspokenlanguage.FieldInternalUserID: + values[i] = new(uuid.UUID) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the ProviderSpokenLanguage fields. +func (_m *ProviderSpokenLanguage) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case providerspokenlanguage.FieldInternalUserID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field internal_user_id", values[i]) + } else if value != nil { + _m.InternalUserID = *value + } + case providerspokenlanguage.FieldLanguageCode: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field language_code", values[i]) + } else if value.Valid { + _m.LanguageCode = value.String + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the ProviderSpokenLanguage. +// This includes values selected through modifiers, order, etc. +func (_m *ProviderSpokenLanguage) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QueryProfile queries the "profile" edge of the ProviderSpokenLanguage entity. +func (_m *ProviderSpokenLanguage) QueryProfile() *ProviderProfileQuery { + return NewProviderSpokenLanguageClient(_m.config).QueryProfile(_m) +} + +// QueryLanguage queries the "language" edge of the ProviderSpokenLanguage entity. +func (_m *ProviderSpokenLanguage) QueryLanguage() *SpokenLanguageQuery { + return NewProviderSpokenLanguageClient(_m.config).QueryLanguage(_m) +} + +// Update returns a builder for updating this ProviderSpokenLanguage. +// Note that you need to call ProviderSpokenLanguage.Unwrap() before calling this method if this ProviderSpokenLanguage +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *ProviderSpokenLanguage) Update() *ProviderSpokenLanguageUpdateOne { + return NewProviderSpokenLanguageClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the ProviderSpokenLanguage entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *ProviderSpokenLanguage) Unwrap() *ProviderSpokenLanguage { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: ProviderSpokenLanguage is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *ProviderSpokenLanguage) String() string { + var builder strings.Builder + builder.WriteString("ProviderSpokenLanguage(") + builder.WriteString("internal_user_id=") + builder.WriteString(fmt.Sprintf("%v", _m.InternalUserID)) + builder.WriteString(", ") + builder.WriteString("language_code=") + builder.WriteString(_m.LanguageCode) + builder.WriteByte(')') + return builder.String() +} + +// ProviderSpokenLanguages is a parsable slice of ProviderSpokenLanguage. +type ProviderSpokenLanguages []*ProviderSpokenLanguage diff --git a/backend/ent/providerspokenlanguage/providerspokenlanguage.go b/backend/ent/providerspokenlanguage/providerspokenlanguage.go new file mode 100644 index 0000000..cac4f35 --- /dev/null +++ b/backend/ent/providerspokenlanguage/providerspokenlanguage.go @@ -0,0 +1,103 @@ +// Code generated by ent, DO NOT EDIT. + +package providerspokenlanguage + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the providerspokenlanguage type in the database. + Label = "provider_spoken_language" + // FieldInternalUserID holds the string denoting the internal_user_id field in the database. + FieldInternalUserID = "internal_user_id" + // FieldLanguageCode holds the string denoting the language_code field in the database. + FieldLanguageCode = "language_code" + // EdgeProfile holds the string denoting the profile edge name in mutations. + EdgeProfile = "profile" + // EdgeLanguage holds the string denoting the language edge name in mutations. + EdgeLanguage = "language" + // ProviderProfileFieldID holds the string denoting the ID field of the ProviderProfile. + ProviderProfileFieldID = "internal_user_id" + // SpokenLanguageFieldID holds the string denoting the ID field of the SpokenLanguage. + SpokenLanguageFieldID = "id" + // Table holds the table name of the providerspokenlanguage in the database. + Table = "provider_spoken_languages" + // ProfileTable is the table that holds the profile relation/edge. + ProfileTable = "provider_spoken_languages" + // ProfileInverseTable is the table name for the ProviderProfile entity. + // It exists in this package in order to avoid circular dependency with the "providerprofile" package. + ProfileInverseTable = "provider_profiles" + // ProfileColumn is the table column denoting the profile relation/edge. + ProfileColumn = "internal_user_id" + // LanguageTable is the table that holds the language relation/edge. + LanguageTable = "provider_spoken_languages" + // LanguageInverseTable is the table name for the SpokenLanguage entity. + // It exists in this package in order to avoid circular dependency with the "spokenlanguage" package. + LanguageInverseTable = "spoken_languages" + // LanguageColumn is the table column denoting the language relation/edge. + LanguageColumn = "language_code" +) + +// Columns holds all SQL columns for providerspokenlanguage fields. +var Columns = []string{ + FieldInternalUserID, + FieldLanguageCode, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // LanguageCodeValidator is a validator for the "language_code" field. It is called by the builders before save. + LanguageCodeValidator func(string) error +) + +// OrderOption defines the ordering options for the ProviderSpokenLanguage queries. +type OrderOption func(*sql.Selector) + +// ByInternalUserID orders the results by the internal_user_id field. +func ByInternalUserID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldInternalUserID, opts...).ToFunc() +} + +// ByLanguageCode orders the results by the language_code field. +func ByLanguageCode(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLanguageCode, opts...).ToFunc() +} + +// ByProfileField orders the results by profile field. +func ByProfileField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newProfileStep(), sql.OrderByField(field, opts...)) + } +} + +// ByLanguageField orders the results by language field. +func ByLanguageField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newLanguageStep(), sql.OrderByField(field, opts...)) + } +} +func newProfileStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, ProfileColumn), + sqlgraph.To(ProfileInverseTable, ProviderProfileFieldID), + sqlgraph.Edge(sqlgraph.M2O, false, ProfileTable, ProfileColumn), + ) +} +func newLanguageStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, LanguageColumn), + sqlgraph.To(LanguageInverseTable, SpokenLanguageFieldID), + sqlgraph.Edge(sqlgraph.M2O, false, LanguageTable, LanguageColumn), + ) +} diff --git a/backend/ent/providerspokenlanguage/where.go b/backend/ent/providerspokenlanguage/where.go new file mode 100644 index 0000000..603315b --- /dev/null +++ b/backend/ent/providerspokenlanguage/where.go @@ -0,0 +1,166 @@ +// Code generated by ent, DO NOT EDIT. + +package providerspokenlanguage + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +// InternalUserID applies equality check predicate on the "internal_user_id" field. It's identical to InternalUserIDEQ. +func InternalUserID(v uuid.UUID) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldEQ(FieldInternalUserID, v)) +} + +// LanguageCode applies equality check predicate on the "language_code" field. It's identical to LanguageCodeEQ. +func LanguageCode(v string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldEQ(FieldLanguageCode, v)) +} + +// InternalUserIDEQ applies the EQ predicate on the "internal_user_id" field. +func InternalUserIDEQ(v uuid.UUID) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldEQ(FieldInternalUserID, v)) +} + +// InternalUserIDNEQ applies the NEQ predicate on the "internal_user_id" field. +func InternalUserIDNEQ(v uuid.UUID) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldNEQ(FieldInternalUserID, v)) +} + +// InternalUserIDIn applies the In predicate on the "internal_user_id" field. +func InternalUserIDIn(vs ...uuid.UUID) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldIn(FieldInternalUserID, vs...)) +} + +// InternalUserIDNotIn applies the NotIn predicate on the "internal_user_id" field. +func InternalUserIDNotIn(vs ...uuid.UUID) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldNotIn(FieldInternalUserID, vs...)) +} + +// LanguageCodeEQ applies the EQ predicate on the "language_code" field. +func LanguageCodeEQ(v string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldEQ(FieldLanguageCode, v)) +} + +// LanguageCodeNEQ applies the NEQ predicate on the "language_code" field. +func LanguageCodeNEQ(v string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldNEQ(FieldLanguageCode, v)) +} + +// LanguageCodeIn applies the In predicate on the "language_code" field. +func LanguageCodeIn(vs ...string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldIn(FieldLanguageCode, vs...)) +} + +// LanguageCodeNotIn applies the NotIn predicate on the "language_code" field. +func LanguageCodeNotIn(vs ...string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldNotIn(FieldLanguageCode, vs...)) +} + +// LanguageCodeGT applies the GT predicate on the "language_code" field. +func LanguageCodeGT(v string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldGT(FieldLanguageCode, v)) +} + +// LanguageCodeGTE applies the GTE predicate on the "language_code" field. +func LanguageCodeGTE(v string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldGTE(FieldLanguageCode, v)) +} + +// LanguageCodeLT applies the LT predicate on the "language_code" field. +func LanguageCodeLT(v string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldLT(FieldLanguageCode, v)) +} + +// LanguageCodeLTE applies the LTE predicate on the "language_code" field. +func LanguageCodeLTE(v string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldLTE(FieldLanguageCode, v)) +} + +// LanguageCodeContains applies the Contains predicate on the "language_code" field. +func LanguageCodeContains(v string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldContains(FieldLanguageCode, v)) +} + +// LanguageCodeHasPrefix applies the HasPrefix predicate on the "language_code" field. +func LanguageCodeHasPrefix(v string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldHasPrefix(FieldLanguageCode, v)) +} + +// LanguageCodeHasSuffix applies the HasSuffix predicate on the "language_code" field. +func LanguageCodeHasSuffix(v string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldHasSuffix(FieldLanguageCode, v)) +} + +// LanguageCodeEqualFold applies the EqualFold predicate on the "language_code" field. +func LanguageCodeEqualFold(v string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldEqualFold(FieldLanguageCode, v)) +} + +// LanguageCodeContainsFold applies the ContainsFold predicate on the "language_code" field. +func LanguageCodeContainsFold(v string) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.FieldContainsFold(FieldLanguageCode, v)) +} + +// HasProfile applies the HasEdge predicate on the "profile" edge. +func HasProfile() predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, ProfileColumn), + sqlgraph.Edge(sqlgraph.M2O, false, ProfileTable, ProfileColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasProfileWith applies the HasEdge predicate on the "profile" edge with a given conditions (other predicates). +func HasProfileWith(preds ...predicate.ProviderProfile) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(func(s *sql.Selector) { + step := newProfileStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasLanguage applies the HasEdge predicate on the "language" edge. +func HasLanguage() predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, LanguageColumn), + sqlgraph.Edge(sqlgraph.M2O, false, LanguageTable, LanguageColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasLanguageWith applies the HasEdge predicate on the "language" edge with a given conditions (other predicates). +func HasLanguageWith(preds ...predicate.SpokenLanguage) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(func(s *sql.Selector) { + step := newLanguageStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.ProviderSpokenLanguage) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.ProviderSpokenLanguage) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.ProviderSpokenLanguage) predicate.ProviderSpokenLanguage { + return predicate.ProviderSpokenLanguage(sql.NotPredicates(p)) +} diff --git a/backend/ent/providerspokenlanguage_create.go b/backend/ent/providerspokenlanguage_create.go new file mode 100644 index 0000000..5d97196 --- /dev/null +++ b/backend/ent/providerspokenlanguage_create.go @@ -0,0 +1,249 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerspokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/google/uuid" +) + +// ProviderSpokenLanguageCreate is the builder for creating a ProviderSpokenLanguage entity. +type ProviderSpokenLanguageCreate struct { + config + mutation *ProviderSpokenLanguageMutation + hooks []Hook +} + +// SetInternalUserID sets the "internal_user_id" field. +func (_c *ProviderSpokenLanguageCreate) SetInternalUserID(v uuid.UUID) *ProviderSpokenLanguageCreate { + _c.mutation.SetInternalUserID(v) + return _c +} + +// SetLanguageCode sets the "language_code" field. +func (_c *ProviderSpokenLanguageCreate) SetLanguageCode(v string) *ProviderSpokenLanguageCreate { + _c.mutation.SetLanguageCode(v) + return _c +} + +// SetProfileID sets the "profile" edge to the ProviderProfile entity by ID. +func (_c *ProviderSpokenLanguageCreate) SetProfileID(id uuid.UUID) *ProviderSpokenLanguageCreate { + _c.mutation.SetProfileID(id) + return _c +} + +// SetProfile sets the "profile" edge to the ProviderProfile entity. +func (_c *ProviderSpokenLanguageCreate) SetProfile(v *ProviderProfile) *ProviderSpokenLanguageCreate { + return _c.SetProfileID(v.ID) +} + +// SetLanguageID sets the "language" edge to the SpokenLanguage entity by ID. +func (_c *ProviderSpokenLanguageCreate) SetLanguageID(id string) *ProviderSpokenLanguageCreate { + _c.mutation.SetLanguageID(id) + return _c +} + +// SetLanguage sets the "language" edge to the SpokenLanguage entity. +func (_c *ProviderSpokenLanguageCreate) SetLanguage(v *SpokenLanguage) *ProviderSpokenLanguageCreate { + return _c.SetLanguageID(v.ID) +} + +// Mutation returns the ProviderSpokenLanguageMutation object of the builder. +func (_c *ProviderSpokenLanguageCreate) Mutation() *ProviderSpokenLanguageMutation { + return _c.mutation +} + +// Save creates the ProviderSpokenLanguage in the database. +func (_c *ProviderSpokenLanguageCreate) Save(ctx context.Context) (*ProviderSpokenLanguage, error) { + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *ProviderSpokenLanguageCreate) SaveX(ctx context.Context) *ProviderSpokenLanguage { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *ProviderSpokenLanguageCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *ProviderSpokenLanguageCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *ProviderSpokenLanguageCreate) check() error { + if _, ok := _c.mutation.InternalUserID(); !ok { + return &ValidationError{Name: "internal_user_id", err: errors.New(`ent: missing required field "ProviderSpokenLanguage.internal_user_id"`)} + } + if _, ok := _c.mutation.LanguageCode(); !ok { + return &ValidationError{Name: "language_code", err: errors.New(`ent: missing required field "ProviderSpokenLanguage.language_code"`)} + } + if v, ok := _c.mutation.LanguageCode(); ok { + if err := providerspokenlanguage.LanguageCodeValidator(v); err != nil { + return &ValidationError{Name: "language_code", err: fmt.Errorf(`ent: validator failed for field "ProviderSpokenLanguage.language_code": %w`, err)} + } + } + if len(_c.mutation.ProfileIDs()) == 0 { + return &ValidationError{Name: "profile", err: errors.New(`ent: missing required edge "ProviderSpokenLanguage.profile"`)} + } + if len(_c.mutation.LanguageIDs()) == 0 { + return &ValidationError{Name: "language", err: errors.New(`ent: missing required edge "ProviderSpokenLanguage.language"`)} + } + return nil +} + +func (_c *ProviderSpokenLanguageCreate) sqlSave(ctx context.Context) (*ProviderSpokenLanguage, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + return _node, nil +} + +func (_c *ProviderSpokenLanguageCreate) createSpec() (*ProviderSpokenLanguage, *sqlgraph.CreateSpec) { + var ( + _node = &ProviderSpokenLanguage{config: _c.config} + _spec = sqlgraph.NewCreateSpec(providerspokenlanguage.Table, nil) + ) + if nodes := _c.mutation.ProfileIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: providerspokenlanguage.ProfileTable, + Columns: []string{providerspokenlanguage.ProfileColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.InternalUserID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.LanguageIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: providerspokenlanguage.LanguageTable, + Columns: []string{providerspokenlanguage.LanguageColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.LanguageCode = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// ProviderSpokenLanguageCreateBulk is the builder for creating many ProviderSpokenLanguage entities in bulk. +type ProviderSpokenLanguageCreateBulk struct { + config + err error + builders []*ProviderSpokenLanguageCreate +} + +// Save creates the ProviderSpokenLanguage entities in the database. +func (_c *ProviderSpokenLanguageCreateBulk) Save(ctx context.Context) ([]*ProviderSpokenLanguage, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*ProviderSpokenLanguage, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*ProviderSpokenLanguageMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *ProviderSpokenLanguageCreateBulk) SaveX(ctx context.Context) []*ProviderSpokenLanguage { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *ProviderSpokenLanguageCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *ProviderSpokenLanguageCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/providerspokenlanguage_delete.go b/backend/ent/providerspokenlanguage_delete.go new file mode 100644 index 0000000..75ebae9 --- /dev/null +++ b/backend/ent/providerspokenlanguage_delete.go @@ -0,0 +1,87 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerspokenlanguage" +) + +// ProviderSpokenLanguageDelete is the builder for deleting a ProviderSpokenLanguage entity. +type ProviderSpokenLanguageDelete struct { + config + hooks []Hook + mutation *ProviderSpokenLanguageMutation +} + +// Where appends a list predicates to the ProviderSpokenLanguageDelete builder. +func (_d *ProviderSpokenLanguageDelete) Where(ps ...predicate.ProviderSpokenLanguage) *ProviderSpokenLanguageDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *ProviderSpokenLanguageDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *ProviderSpokenLanguageDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *ProviderSpokenLanguageDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(providerspokenlanguage.Table, nil) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// ProviderSpokenLanguageDeleteOne is the builder for deleting a single ProviderSpokenLanguage entity. +type ProviderSpokenLanguageDeleteOne struct { + _d *ProviderSpokenLanguageDelete +} + +// Where appends a list predicates to the ProviderSpokenLanguageDelete builder. +func (_d *ProviderSpokenLanguageDeleteOne) Where(ps ...predicate.ProviderSpokenLanguage) *ProviderSpokenLanguageDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *ProviderSpokenLanguageDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{providerspokenlanguage.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *ProviderSpokenLanguageDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/providerspokenlanguage_query.go b/backend/ent/providerspokenlanguage_query.go new file mode 100644 index 0000000..590211d --- /dev/null +++ b/backend/ent/providerspokenlanguage_query.go @@ -0,0 +1,604 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerspokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/google/uuid" +) + +// ProviderSpokenLanguageQuery is the builder for querying ProviderSpokenLanguage entities. +type ProviderSpokenLanguageQuery struct { + config + ctx *QueryContext + order []providerspokenlanguage.OrderOption + inters []Interceptor + predicates []predicate.ProviderSpokenLanguage + withProfile *ProviderProfileQuery + withLanguage *SpokenLanguageQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the ProviderSpokenLanguageQuery builder. +func (_q *ProviderSpokenLanguageQuery) Where(ps ...predicate.ProviderSpokenLanguage) *ProviderSpokenLanguageQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *ProviderSpokenLanguageQuery) Limit(limit int) *ProviderSpokenLanguageQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *ProviderSpokenLanguageQuery) Offset(offset int) *ProviderSpokenLanguageQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *ProviderSpokenLanguageQuery) Unique(unique bool) *ProviderSpokenLanguageQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *ProviderSpokenLanguageQuery) Order(o ...providerspokenlanguage.OrderOption) *ProviderSpokenLanguageQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QueryProfile chains the current query on the "profile" edge. +func (_q *ProviderSpokenLanguageQuery) QueryProfile() *ProviderProfileQuery { + query := (&ProviderProfileClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(providerspokenlanguage.Table, providerspokenlanguage.ProfileColumn, selector), + sqlgraph.To(providerprofile.Table, providerprofile.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, providerspokenlanguage.ProfileTable, providerspokenlanguage.ProfileColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryLanguage chains the current query on the "language" edge. +func (_q *ProviderSpokenLanguageQuery) QueryLanguage() *SpokenLanguageQuery { + query := (&SpokenLanguageClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(providerspokenlanguage.Table, providerspokenlanguage.LanguageColumn, selector), + sqlgraph.To(spokenlanguage.Table, spokenlanguage.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, providerspokenlanguage.LanguageTable, providerspokenlanguage.LanguageColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first ProviderSpokenLanguage entity from the query. +// Returns a *NotFoundError when no ProviderSpokenLanguage was found. +func (_q *ProviderSpokenLanguageQuery) First(ctx context.Context) (*ProviderSpokenLanguage, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{providerspokenlanguage.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *ProviderSpokenLanguageQuery) FirstX(ctx context.Context) *ProviderSpokenLanguage { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// Only returns a single ProviderSpokenLanguage entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one ProviderSpokenLanguage entity is found. +// Returns a *NotFoundError when no ProviderSpokenLanguage entities are found. +func (_q *ProviderSpokenLanguageQuery) Only(ctx context.Context) (*ProviderSpokenLanguage, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{providerspokenlanguage.Label} + default: + return nil, &NotSingularError{providerspokenlanguage.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *ProviderSpokenLanguageQuery) OnlyX(ctx context.Context) *ProviderSpokenLanguage { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// All executes the query and returns a list of ProviderSpokenLanguages. +func (_q *ProviderSpokenLanguageQuery) All(ctx context.Context) ([]*ProviderSpokenLanguage, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*ProviderSpokenLanguage, *ProviderSpokenLanguageQuery]() + return withInterceptors[[]*ProviderSpokenLanguage](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *ProviderSpokenLanguageQuery) AllX(ctx context.Context) []*ProviderSpokenLanguage { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// Count returns the count of the given query. +func (_q *ProviderSpokenLanguageQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*ProviderSpokenLanguageQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *ProviderSpokenLanguageQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *ProviderSpokenLanguageQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.First(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *ProviderSpokenLanguageQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the ProviderSpokenLanguageQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *ProviderSpokenLanguageQuery) Clone() *ProviderSpokenLanguageQuery { + if _q == nil { + return nil + } + return &ProviderSpokenLanguageQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]providerspokenlanguage.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.ProviderSpokenLanguage{}, _q.predicates...), + withProfile: _q.withProfile.Clone(), + withLanguage: _q.withLanguage.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// WithProfile tells the query-builder to eager-load the nodes that are connected to +// the "profile" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ProviderSpokenLanguageQuery) WithProfile(opts ...func(*ProviderProfileQuery)) *ProviderSpokenLanguageQuery { + query := (&ProviderProfileClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withProfile = query + return _q +} + +// WithLanguage tells the query-builder to eager-load the nodes that are connected to +// the "language" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ProviderSpokenLanguageQuery) WithLanguage(opts ...func(*SpokenLanguageQuery)) *ProviderSpokenLanguageQuery { + query := (&SpokenLanguageClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withLanguage = query + return _q +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// InternalUserID uuid.UUID `json:"internal_user_id,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.ProviderSpokenLanguage.Query(). +// GroupBy(providerspokenlanguage.FieldInternalUserID). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *ProviderSpokenLanguageQuery) GroupBy(field string, fields ...string) *ProviderSpokenLanguageGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &ProviderSpokenLanguageGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = providerspokenlanguage.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// InternalUserID uuid.UUID `json:"internal_user_id,omitempty"` +// } +// +// client.ProviderSpokenLanguage.Query(). +// Select(providerspokenlanguage.FieldInternalUserID). +// Scan(ctx, &v) +func (_q *ProviderSpokenLanguageQuery) Select(fields ...string) *ProviderSpokenLanguageSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &ProviderSpokenLanguageSelect{ProviderSpokenLanguageQuery: _q} + sbuild.label = providerspokenlanguage.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a ProviderSpokenLanguageSelect configured with the given aggregations. +func (_q *ProviderSpokenLanguageQuery) Aggregate(fns ...AggregateFunc) *ProviderSpokenLanguageSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *ProviderSpokenLanguageQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !providerspokenlanguage.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *ProviderSpokenLanguageQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ProviderSpokenLanguage, error) { + var ( + nodes = []*ProviderSpokenLanguage{} + _spec = _q.querySpec() + loadedTypes = [2]bool{ + _q.withProfile != nil, + _q.withLanguage != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*ProviderSpokenLanguage).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &ProviderSpokenLanguage{config: _q.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := _q.withProfile; query != nil { + if err := _q.loadProfile(ctx, query, nodes, nil, + func(n *ProviderSpokenLanguage, e *ProviderProfile) { n.Edges.Profile = e }); err != nil { + return nil, err + } + } + if query := _q.withLanguage; query != nil { + if err := _q.loadLanguage(ctx, query, nodes, nil, + func(n *ProviderSpokenLanguage, e *SpokenLanguage) { n.Edges.Language = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *ProviderSpokenLanguageQuery) loadProfile(ctx context.Context, query *ProviderProfileQuery, nodes []*ProviderSpokenLanguage, init func(*ProviderSpokenLanguage), assign func(*ProviderSpokenLanguage, *ProviderProfile)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*ProviderSpokenLanguage) + for i := range nodes { + fk := nodes[i].InternalUserID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(providerprofile.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "internal_user_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (_q *ProviderSpokenLanguageQuery) loadLanguage(ctx context.Context, query *SpokenLanguageQuery, nodes []*ProviderSpokenLanguage, init func(*ProviderSpokenLanguage), assign func(*ProviderSpokenLanguage, *SpokenLanguage)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*ProviderSpokenLanguage) + for i := range nodes { + fk := nodes[i].LanguageCode + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(spokenlanguage.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "language_code" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (_q *ProviderSpokenLanguageQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Unique = false + _spec.Node.Columns = nil + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *ProviderSpokenLanguageQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(providerspokenlanguage.Table, providerspokenlanguage.Columns, nil) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + for i := range fields { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + if _q.withProfile != nil { + _spec.Node.AddColumnOnce(providerspokenlanguage.FieldInternalUserID) + } + if _q.withLanguage != nil { + _spec.Node.AddColumnOnce(providerspokenlanguage.FieldLanguageCode) + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *ProviderSpokenLanguageQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(providerspokenlanguage.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = providerspokenlanguage.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ProviderSpokenLanguageGroupBy is the group-by builder for ProviderSpokenLanguage entities. +type ProviderSpokenLanguageGroupBy struct { + selector + build *ProviderSpokenLanguageQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *ProviderSpokenLanguageGroupBy) Aggregate(fns ...AggregateFunc) *ProviderSpokenLanguageGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *ProviderSpokenLanguageGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ProviderSpokenLanguageQuery, *ProviderSpokenLanguageGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *ProviderSpokenLanguageGroupBy) sqlScan(ctx context.Context, root *ProviderSpokenLanguageQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// ProviderSpokenLanguageSelect is the builder for selecting fields of ProviderSpokenLanguage entities. +type ProviderSpokenLanguageSelect struct { + *ProviderSpokenLanguageQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *ProviderSpokenLanguageSelect) Aggregate(fns ...AggregateFunc) *ProviderSpokenLanguageSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *ProviderSpokenLanguageSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ProviderSpokenLanguageQuery, *ProviderSpokenLanguageSelect](ctx, _s.ProviderSpokenLanguageQuery, _s, _s.inters, v) +} + +func (_s *ProviderSpokenLanguageSelect) sqlScan(ctx context.Context, root *ProviderSpokenLanguageQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/providerspokenlanguage_update.go b/backend/ent/providerspokenlanguage_update.go new file mode 100644 index 0000000..02836d9 --- /dev/null +++ b/backend/ent/providerspokenlanguage_update.go @@ -0,0 +1,205 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerspokenlanguage" +) + +// ProviderSpokenLanguageUpdate is the builder for updating ProviderSpokenLanguage entities. +type ProviderSpokenLanguageUpdate struct { + config + hooks []Hook + mutation *ProviderSpokenLanguageMutation +} + +// Where appends a list predicates to the ProviderSpokenLanguageUpdate builder. +func (_u *ProviderSpokenLanguageUpdate) Where(ps ...predicate.ProviderSpokenLanguage) *ProviderSpokenLanguageUpdate { + _u.mutation.Where(ps...) + return _u +} + +// Mutation returns the ProviderSpokenLanguageMutation object of the builder. +func (_u *ProviderSpokenLanguageUpdate) Mutation() *ProviderSpokenLanguageMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *ProviderSpokenLanguageUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *ProviderSpokenLanguageUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *ProviderSpokenLanguageUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *ProviderSpokenLanguageUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *ProviderSpokenLanguageUpdate) check() error { + if _u.mutation.ProfileCleared() && len(_u.mutation.ProfileIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "ProviderSpokenLanguage.profile"`) + } + if _u.mutation.LanguageCleared() && len(_u.mutation.LanguageIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "ProviderSpokenLanguage.language"`) + } + return nil +} + +func (_u *ProviderSpokenLanguageUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(providerspokenlanguage.Table, providerspokenlanguage.Columns, sqlgraph.NewFieldSpec(providerspokenlanguage.FieldInternalUserID, field.TypeUUID), sqlgraph.NewFieldSpec(providerspokenlanguage.FieldLanguageCode, field.TypeString)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{providerspokenlanguage.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// ProviderSpokenLanguageUpdateOne is the builder for updating a single ProviderSpokenLanguage entity. +type ProviderSpokenLanguageUpdateOne struct { + config + fields []string + hooks []Hook + mutation *ProviderSpokenLanguageMutation +} + +// Mutation returns the ProviderSpokenLanguageMutation object of the builder. +func (_u *ProviderSpokenLanguageUpdateOne) Mutation() *ProviderSpokenLanguageMutation { + return _u.mutation +} + +// Where appends a list predicates to the ProviderSpokenLanguageUpdate builder. +func (_u *ProviderSpokenLanguageUpdateOne) Where(ps ...predicate.ProviderSpokenLanguage) *ProviderSpokenLanguageUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *ProviderSpokenLanguageUpdateOne) Select(field string, fields ...string) *ProviderSpokenLanguageUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated ProviderSpokenLanguage entity. +func (_u *ProviderSpokenLanguageUpdateOne) Save(ctx context.Context) (*ProviderSpokenLanguage, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *ProviderSpokenLanguageUpdateOne) SaveX(ctx context.Context) *ProviderSpokenLanguage { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *ProviderSpokenLanguageUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *ProviderSpokenLanguageUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *ProviderSpokenLanguageUpdateOne) check() error { + if _u.mutation.ProfileCleared() && len(_u.mutation.ProfileIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "ProviderSpokenLanguage.profile"`) + } + if _u.mutation.LanguageCleared() && len(_u.mutation.LanguageIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "ProviderSpokenLanguage.language"`) + } + return nil +} + +func (_u *ProviderSpokenLanguageUpdateOne) sqlSave(ctx context.Context) (_node *ProviderSpokenLanguage, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(providerspokenlanguage.Table, providerspokenlanguage.Columns, sqlgraph.NewFieldSpec(providerspokenlanguage.FieldInternalUserID, field.TypeUUID), sqlgraph.NewFieldSpec(providerspokenlanguage.FieldLanguageCode, field.TypeString)) + if id, ok := _u.mutation.InternalUserID(); !ok { + return nil, &ValidationError{Name: "internal_user_id", err: errors.New(`ent: missing "ProviderSpokenLanguage.internal_user_id" for update`)} + } else { + _spec.Node.CompositeID[0].Value = id + } + if id, ok := _u.mutation.LanguageCode(); !ok { + return nil, &ValidationError{Name: "language_code", err: errors.New(`ent: missing "ProviderSpokenLanguage.language_code" for update`)} + } else { + _spec.Node.CompositeID[1].Value = id + } + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, len(fields)) + for i, f := range fields { + if !providerspokenlanguage.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + _spec.Node.Columns[i] = f + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + _node = &ProviderSpokenLanguage{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{providerspokenlanguage.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/ent/runtime.go b/backend/ent/runtime.go index af94806..4d91201 100644 --- a/backend/ent/runtime.go +++ b/backend/ent/runtime.go @@ -5,8 +5,17 @@ package ent import ( "time" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/administrativearea" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/internaluser" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerspokenlanguage" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/schema" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategorytranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguagetranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" "github.com/SourceSenseiTheRealOne/juntly/backend/ent/useraccount" "github.com/google/uuid" ) @@ -15,6 +24,116 @@ import ( // (default values, validators, hooks and policies) and stitches it // to their package variables. func init() { + administrativeareaFields := schema.AdministrativeArea{}.Fields() + _ = administrativeareaFields + // administrativeareaDescSource is the schema descriptor for source field. + administrativeareaDescSource := administrativeareaFields[1].Descriptor() + // administrativearea.SourceValidator is a validator for the "source" field. It is called by the builders before save. + administrativearea.SourceValidator = func() func(string) error { + validators := administrativeareaDescSource.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(source string) error { + for _, fn := range fns { + if err := fn(source); err != nil { + return err + } + } + return nil + } + }() + // administrativeareaDescSourceVersion is the schema descriptor for source_version field. + administrativeareaDescSourceVersion := administrativeareaFields[2].Descriptor() + // administrativearea.SourceVersionValidator is a validator for the "source_version" field. It is called by the builders before save. + administrativearea.SourceVersionValidator = func() func(string) error { + validators := administrativeareaDescSourceVersion.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(source_version string) error { + for _, fn := range fns { + if err := fn(source_version); err != nil { + return err + } + } + return nil + } + }() + // administrativeareaDescExternalCode is the schema descriptor for external_code field. + administrativeareaDescExternalCode := administrativeareaFields[3].Descriptor() + // administrativearea.ExternalCodeValidator is a validator for the "external_code" field. It is called by the builders before save. + administrativearea.ExternalCodeValidator = func() func(string) error { + validators := administrativeareaDescExternalCode.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(external_code string) error { + for _, fn := range fns { + if err := fn(external_code); err != nil { + return err + } + } + return nil + } + }() + // administrativeareaDescKind is the schema descriptor for kind field. + administrativeareaDescKind := administrativeareaFields[4].Descriptor() + // administrativearea.KindValidator is a validator for the "kind" field. It is called by the builders before save. + administrativearea.KindValidator = func() func(string) error { + validators := administrativeareaDescKind.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(kind string) error { + for _, fn := range fns { + if err := fn(kind); err != nil { + return err + } + } + return nil + } + }() + // administrativeareaDescName is the schema descriptor for name field. + administrativeareaDescName := administrativeareaFields[5].Descriptor() + // administrativearea.NameValidator is a validator for the "name" field. It is called by the builders before save. + administrativearea.NameValidator = func() func(string) error { + validators := administrativeareaDescName.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(name string) error { + for _, fn := range fns { + if err := fn(name); err != nil { + return err + } + } + return nil + } + }() + // administrativeareaDescActive is the schema descriptor for active field. + administrativeareaDescActive := administrativeareaFields[7].Descriptor() + // administrativearea.DefaultActive holds the default value on creation for the active field. + administrativearea.DefaultActive = administrativeareaDescActive.Default.(bool) + // administrativeareaDescCreatedAt is the schema descriptor for created_at field. + administrativeareaDescCreatedAt := administrativeareaFields[8].Descriptor() + // administrativearea.DefaultCreatedAt holds the default value on creation for the created_at field. + administrativearea.DefaultCreatedAt = administrativeareaDescCreatedAt.Default.(func() time.Time) + // administrativeareaDescUpdatedAt is the schema descriptor for updated_at field. + administrativeareaDescUpdatedAt := administrativeareaFields[9].Descriptor() + // administrativearea.DefaultUpdatedAt holds the default value on creation for the updated_at field. + administrativearea.DefaultUpdatedAt = administrativeareaDescUpdatedAt.Default.(func() time.Time) + // administrativearea.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + administrativearea.UpdateDefaultUpdatedAt = administrativeareaDescUpdatedAt.UpdateDefault.(func() time.Time) + // administrativeareaDescID is the schema descriptor for id field. + administrativeareaDescID := administrativeareaFields[0].Descriptor() + // administrativearea.DefaultID holds the default value on creation for the id field. + administrativearea.DefaultID = administrativeareaDescID.Default.(func() uuid.UUID) internaluserFields := schema.InternalUser{}.Fields() _ = internaluserFields // internaluserDescClerkSubject is the schema descriptor for clerk_subject field. @@ -49,6 +168,450 @@ func init() { internaluserDescID := internaluserFields[0].Descriptor() // internaluser.DefaultID holds the default value on creation for the id field. internaluser.DefaultID = internaluserDescID.Default.(func() uuid.UUID) + localityFields := schema.Locality{}.Fields() + _ = localityFields + // localityDescSlug is the schema descriptor for slug field. + localityDescSlug := localityFields[1].Descriptor() + // locality.SlugValidator is a validator for the "slug" field. It is called by the builders before save. + locality.SlugValidator = func() func(string) error { + validators := localityDescSlug.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(slug string) error { + for _, fn := range fns { + if err := fn(slug); err != nil { + return err + } + } + return nil + } + }() + // localityDescName is the schema descriptor for name field. + localityDescName := localityFields[2].Descriptor() + // locality.NameValidator is a validator for the "name" field. It is called by the builders before save. + locality.NameValidator = func() func(string) error { + validators := localityDescName.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(name string) error { + for _, fn := range fns { + if err := fn(name); err != nil { + return err + } + } + return nil + } + }() + // localityDescSource is the schema descriptor for source field. + localityDescSource := localityFields[4].Descriptor() + // locality.SourceValidator is a validator for the "source" field. It is called by the builders before save. + locality.SourceValidator = func() func(string) error { + validators := localityDescSource.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(source string) error { + for _, fn := range fns { + if err := fn(source); err != nil { + return err + } + } + return nil + } + }() + // localityDescSourceElementID is the schema descriptor for source_element_id field. + localityDescSourceElementID := localityFields[5].Descriptor() + // locality.SourceElementIDValidator is a validator for the "source_element_id" field. It is called by the builders before save. + locality.SourceElementIDValidator = func() func(string) error { + validators := localityDescSourceElementID.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(source_element_id string) error { + for _, fn := range fns { + if err := fn(source_element_id); err != nil { + return err + } + } + return nil + } + }() + // localityDescSourceVersion is the schema descriptor for source_version field. + localityDescSourceVersion := localityFields[6].Descriptor() + // locality.SourceVersionValidator is a validator for the "source_version" field. It is called by the builders before save. + locality.SourceVersionValidator = func() func(string) error { + validators := localityDescSourceVersion.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(source_version string) error { + for _, fn := range fns { + if err := fn(source_version); err != nil { + return err + } + } + return nil + } + }() + // localityDescLatitude is the schema descriptor for latitude field. + localityDescLatitude := localityFields[8].Descriptor() + // locality.LatitudeValidator is a validator for the "latitude" field. It is called by the builders before save. + locality.LatitudeValidator = func() func(float64) error { + validators := localityDescLatitude.Validators + fns := [...]func(float64) error{ + validators[0].(func(float64) error), + validators[1].(func(float64) error), + } + return func(latitude float64) error { + for _, fn := range fns { + if err := fn(latitude); err != nil { + return err + } + } + return nil + } + }() + // localityDescLongitude is the schema descriptor for longitude field. + localityDescLongitude := localityFields[9].Descriptor() + // locality.LongitudeValidator is a validator for the "longitude" field. It is called by the builders before save. + locality.LongitudeValidator = func() func(float64) error { + validators := localityDescLongitude.Validators + fns := [...]func(float64) error{ + validators[0].(func(float64) error), + validators[1].(func(float64) error), + } + return func(longitude float64) error { + for _, fn := range fns { + if err := fn(longitude); err != nil { + return err + } + } + return nil + } + }() + // localityDescActive is the schema descriptor for active field. + localityDescActive := localityFields[10].Descriptor() + // locality.DefaultActive holds the default value on creation for the active field. + locality.DefaultActive = localityDescActive.Default.(bool) + // localityDescCreatedAt is the schema descriptor for created_at field. + localityDescCreatedAt := localityFields[11].Descriptor() + // locality.DefaultCreatedAt holds the default value on creation for the created_at field. + locality.DefaultCreatedAt = localityDescCreatedAt.Default.(func() time.Time) + // localityDescUpdatedAt is the schema descriptor for updated_at field. + localityDescUpdatedAt := localityFields[12].Descriptor() + // locality.DefaultUpdatedAt holds the default value on creation for the updated_at field. + locality.DefaultUpdatedAt = localityDescUpdatedAt.Default.(func() time.Time) + // locality.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + locality.UpdateDefaultUpdatedAt = localityDescUpdatedAt.UpdateDefault.(func() time.Time) + // localityDescID is the schema descriptor for id field. + localityDescID := localityFields[0].Descriptor() + // locality.DefaultID holds the default value on creation for the id field. + locality.DefaultID = localityDescID.Default.(func() uuid.UUID) + providerprofileFields := schema.ProviderProfile{}.Fields() + _ = providerprofileFields + // providerprofileDescDisplayName is the schema descriptor for display_name field. + providerprofileDescDisplayName := providerprofileFields[1].Descriptor() + // providerprofile.DisplayNameValidator is a validator for the "display_name" field. It is called by the builders before save. + providerprofile.DisplayNameValidator = func() func(string) error { + validators := providerprofileDescDisplayName.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(display_name string) error { + for _, fn := range fns { + if err := fn(display_name); err != nil { + return err + } + } + return nil + } + }() + // providerprofileDescProviderType is the schema descriptor for provider_type field. + providerprofileDescProviderType := providerprofileFields[2].Descriptor() + // providerprofile.ProviderTypeValidator is a validator for the "provider_type" field. It is called by the builders before save. + providerprofile.ProviderTypeValidator = func() func(string) error { + validators := providerprofileDescProviderType.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(provider_type string) error { + for _, fn := range fns { + if err := fn(provider_type); err != nil { + return err + } + } + return nil + } + }() + // providerprofileDescBio is the schema descriptor for bio field. + providerprofileDescBio := providerprofileFields[3].Descriptor() + // providerprofile.BioValidator is a validator for the "bio" field. It is called by the builders before save. + providerprofile.BioValidator = providerprofileDescBio.Validators[0].(func(string) error) + // providerprofileDescMaxTravelDistanceKm is the schema descriptor for max_travel_distance_km field. + providerprofileDescMaxTravelDistanceKm := providerprofileFields[5].Descriptor() + // providerprofile.MaxTravelDistanceKmValidator is a validator for the "max_travel_distance_km" field. It is called by the builders before save. + providerprofile.MaxTravelDistanceKmValidator = func() func(int) error { + validators := providerprofileDescMaxTravelDistanceKm.Validators + fns := [...]func(int) error{ + validators[0].(func(int) error), + validators[1].(func(int) error), + } + return func(max_travel_distance_km int) error { + for _, fn := range fns { + if err := fn(max_travel_distance_km); err != nil { + return err + } + } + return nil + } + }() + // providerprofileDescTravelsToCustomer is the schema descriptor for travels_to_customer field. + providerprofileDescTravelsToCustomer := providerprofileFields[6].Descriptor() + // providerprofile.DefaultTravelsToCustomer holds the default value on creation for the travels_to_customer field. + providerprofile.DefaultTravelsToCustomer = providerprofileDescTravelsToCustomer.Default.(bool) + // providerprofileDescReceivesCustomer is the schema descriptor for receives_customer field. + providerprofileDescReceivesCustomer := providerprofileFields[7].Descriptor() + // providerprofile.DefaultReceivesCustomer holds the default value on creation for the receives_customer field. + providerprofile.DefaultReceivesCustomer = providerprofileDescReceivesCustomer.Default.(bool) + // providerprofileDescRemoteServices is the schema descriptor for remote_services field. + providerprofileDescRemoteServices := providerprofileFields[8].Descriptor() + // providerprofile.DefaultRemoteServices holds the default value on creation for the remote_services field. + providerprofile.DefaultRemoteServices = providerprofileDescRemoteServices.Default.(bool) + // providerprofileDescCreatedAt is the schema descriptor for created_at field. + providerprofileDescCreatedAt := providerprofileFields[9].Descriptor() + // providerprofile.DefaultCreatedAt holds the default value on creation for the created_at field. + providerprofile.DefaultCreatedAt = providerprofileDescCreatedAt.Default.(func() time.Time) + // providerprofileDescUpdatedAt is the schema descriptor for updated_at field. + providerprofileDescUpdatedAt := providerprofileFields[10].Descriptor() + // providerprofile.DefaultUpdatedAt holds the default value on creation for the updated_at field. + providerprofile.DefaultUpdatedAt = providerprofileDescUpdatedAt.Default.(func() time.Time) + // providerprofile.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + providerprofile.UpdateDefaultUpdatedAt = providerprofileDescUpdatedAt.UpdateDefault.(func() time.Time) + providerspokenlanguageFields := schema.ProviderSpokenLanguage{}.Fields() + _ = providerspokenlanguageFields + // providerspokenlanguageDescLanguageCode is the schema descriptor for language_code field. + providerspokenlanguageDescLanguageCode := providerspokenlanguageFields[1].Descriptor() + // providerspokenlanguage.LanguageCodeValidator is a validator for the "language_code" field. It is called by the builders before save. + providerspokenlanguage.LanguageCodeValidator = func() func(string) error { + validators := providerspokenlanguageDescLanguageCode.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(language string) error { + for _, fn := range fns { + if err := fn(language); err != nil { + return err + } + } + return nil + } + }() + servicecategoryFields := schema.ServiceCategory{}.Fields() + _ = servicecategoryFields + // servicecategoryDescSlug is the schema descriptor for slug field. + servicecategoryDescSlug := servicecategoryFields[2].Descriptor() + // servicecategory.SlugValidator is a validator for the "slug" field. It is called by the builders before save. + servicecategory.SlugValidator = func() func(string) error { + validators := servicecategoryDescSlug.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(slug string) error { + for _, fn := range fns { + if err := fn(slug); err != nil { + return err + } + } + return nil + } + }() + // servicecategoryDescActive is the schema descriptor for active field. + servicecategoryDescActive := servicecategoryFields[3].Descriptor() + // servicecategory.DefaultActive holds the default value on creation for the active field. + servicecategory.DefaultActive = servicecategoryDescActive.Default.(bool) + // servicecategoryDescSortOrder is the schema descriptor for sort_order field. + servicecategoryDescSortOrder := servicecategoryFields[4].Descriptor() + // servicecategory.SortOrderValidator is a validator for the "sort_order" field. It is called by the builders before save. + servicecategory.SortOrderValidator = servicecategoryDescSortOrder.Validators[0].(func(int) error) + // servicecategoryDescCreatedAt is the schema descriptor for created_at field. + servicecategoryDescCreatedAt := servicecategoryFields[5].Descriptor() + // servicecategory.DefaultCreatedAt holds the default value on creation for the created_at field. + servicecategory.DefaultCreatedAt = servicecategoryDescCreatedAt.Default.(func() time.Time) + // servicecategoryDescUpdatedAt is the schema descriptor for updated_at field. + servicecategoryDescUpdatedAt := servicecategoryFields[6].Descriptor() + // servicecategory.DefaultUpdatedAt holds the default value on creation for the updated_at field. + servicecategory.DefaultUpdatedAt = servicecategoryDescUpdatedAt.Default.(func() time.Time) + // servicecategory.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + servicecategory.UpdateDefaultUpdatedAt = servicecategoryDescUpdatedAt.UpdateDefault.(func() time.Time) + // servicecategoryDescID is the schema descriptor for id field. + servicecategoryDescID := servicecategoryFields[0].Descriptor() + // servicecategory.DefaultID holds the default value on creation for the id field. + servicecategory.DefaultID = servicecategoryDescID.Default.(func() uuid.UUID) + servicecategorytranslationFields := schema.ServiceCategoryTranslation{}.Fields() + _ = servicecategorytranslationFields + // servicecategorytranslationDescLocale is the schema descriptor for locale field. + servicecategorytranslationDescLocale := servicecategorytranslationFields[1].Descriptor() + // servicecategorytranslation.LocaleValidator is a validator for the "locale" field. It is called by the builders before save. + servicecategorytranslation.LocaleValidator = func() func(string) error { + validators := servicecategorytranslationDescLocale.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(locale_record string) error { + for _, fn := range fns { + if err := fn(locale_record); err != nil { + return err + } + } + return nil + } + }() + // servicecategorytranslationDescName is the schema descriptor for name field. + servicecategorytranslationDescName := servicecategorytranslationFields[2].Descriptor() + // servicecategorytranslation.NameValidator is a validator for the "name" field. It is called by the builders before save. + servicecategorytranslation.NameValidator = func() func(string) error { + validators := servicecategorytranslationDescName.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(name string) error { + for _, fn := range fns { + if err := fn(name); err != nil { + return err + } + } + return nil + } + }() + // servicecategorytranslationDescDescription is the schema descriptor for description field. + servicecategorytranslationDescDescription := servicecategorytranslationFields[3].Descriptor() + // servicecategorytranslation.DescriptionValidator is a validator for the "description" field. It is called by the builders before save. + servicecategorytranslation.DescriptionValidator = servicecategorytranslationDescDescription.Validators[0].(func(string) error) + spokenlanguageFields := schema.SpokenLanguage{}.Fields() + _ = spokenlanguageFields + // spokenlanguageDescActive is the schema descriptor for active field. + spokenlanguageDescActive := spokenlanguageFields[1].Descriptor() + // spokenlanguage.DefaultActive holds the default value on creation for the active field. + spokenlanguage.DefaultActive = spokenlanguageDescActive.Default.(bool) + // spokenlanguageDescSortOrder is the schema descriptor for sort_order field. + spokenlanguageDescSortOrder := spokenlanguageFields[2].Descriptor() + // spokenlanguage.SortOrderValidator is a validator for the "sort_order" field. It is called by the builders before save. + spokenlanguage.SortOrderValidator = spokenlanguageDescSortOrder.Validators[0].(func(int) error) + // spokenlanguageDescID is the schema descriptor for id field. + spokenlanguageDescID := spokenlanguageFields[0].Descriptor() + // spokenlanguage.IDValidator is a validator for the "id" field. It is called by the builders before save. + spokenlanguage.IDValidator = func() func(string) error { + validators := spokenlanguageDescID.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(id string) error { + for _, fn := range fns { + if err := fn(id); err != nil { + return err + } + } + return nil + } + }() + spokenlanguagetranslationFields := schema.SpokenLanguageTranslation{}.Fields() + _ = spokenlanguagetranslationFields + // spokenlanguagetranslationDescLanguageCode is the schema descriptor for language_code field. + spokenlanguagetranslationDescLanguageCode := spokenlanguagetranslationFields[0].Descriptor() + // spokenlanguagetranslation.LanguageCodeValidator is a validator for the "language_code" field. It is called by the builders before save. + spokenlanguagetranslation.LanguageCodeValidator = func() func(string) error { + validators := spokenlanguagetranslationDescLanguageCode.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(language string) error { + for _, fn := range fns { + if err := fn(language); err != nil { + return err + } + } + return nil + } + }() + // spokenlanguagetranslationDescLocale is the schema descriptor for locale field. + spokenlanguagetranslationDescLocale := spokenlanguagetranslationFields[1].Descriptor() + // spokenlanguagetranslation.LocaleValidator is a validator for the "locale" field. It is called by the builders before save. + spokenlanguagetranslation.LocaleValidator = func() func(string) error { + validators := spokenlanguagetranslationDescLocale.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(locale_record string) error { + for _, fn := range fns { + if err := fn(locale_record); err != nil { + return err + } + } + return nil + } + }() + // spokenlanguagetranslationDescName is the schema descriptor for name field. + spokenlanguagetranslationDescName := spokenlanguagetranslationFields[2].Descriptor() + // spokenlanguagetranslation.NameValidator is a validator for the "name" field. It is called by the builders before save. + spokenlanguagetranslation.NameValidator = func() func(string) error { + validators := spokenlanguagetranslationDescName.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(name string) error { + for _, fn := range fns { + if err := fn(name); err != nil { + return err + } + } + return nil + } + }() + supportedlocaleFields := schema.SupportedLocale{}.Fields() + _ = supportedlocaleFields + // supportedlocaleDescActive is the schema descriptor for active field. + supportedlocaleDescActive := supportedlocaleFields[1].Descriptor() + // supportedlocale.DefaultActive holds the default value on creation for the active field. + supportedlocale.DefaultActive = supportedlocaleDescActive.Default.(bool) + // supportedlocaleDescSortOrder is the schema descriptor for sort_order field. + supportedlocaleDescSortOrder := supportedlocaleFields[2].Descriptor() + // supportedlocale.SortOrderValidator is a validator for the "sort_order" field. It is called by the builders before save. + supportedlocale.SortOrderValidator = supportedlocaleDescSortOrder.Validators[0].(func(int) error) + // supportedlocaleDescID is the schema descriptor for id field. + supportedlocaleDescID := supportedlocaleFields[0].Descriptor() + // supportedlocale.IDValidator is a validator for the "id" field. It is called by the builders before save. + supportedlocale.IDValidator = func() func(string) error { + validators := supportedlocaleDescID.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(id string) error { + for _, fn := range fns { + if err := fn(id); err != nil { + return err + } + } + return nil + } + }() useraccountFields := schema.UserAccount{}.Fields() _ = useraccountFields // useraccountDescProviderEnabled is the schema descriptor for provider_enabled field. diff --git a/backend/ent/schema/administrativearea.go b/backend/ent/schema/administrativearea.go new file mode 100644 index 0000000..9ffdfb5 --- /dev/null +++ b/backend/ent/schema/administrativearea.go @@ -0,0 +1,47 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect/entsql" + entschema "entgo.io/ent/schema" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "github.com/google/uuid" +) + +type AdministrativeArea struct{ ent.Schema } + +func (AdministrativeArea) Fields() []ent.Field { + return []ent.Field{ + field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable(), + field.String("source").NotEmpty().MaxLen(40).Immutable(), + field.String("source_version").NotEmpty().MaxLen(20).Immutable(), + field.String("external_code").NotEmpty().MaxLen(32).Immutable(), + field.String("kind").NotEmpty().MaxLen(20).Immutable(), + field.String("name").NotEmpty().MaxLen(160), + field.UUID("parent_id", uuid.UUID{}).Optional().Nillable().Immutable(), + field.Bool("active").Default(true), + field.Time("created_at").Default(utcNow).Immutable(), + field.Time("updated_at").Default(utcNow).UpdateDefault(utcNow), + } +} + +func (AdministrativeArea) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("children", AdministrativeArea.Type), + edge.From("parent", AdministrativeArea.Type).Ref("children").Field("parent_id").Unique().Immutable(), + edge.To("localities", Locality.Type), + } +} + +func (AdministrativeArea) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("source", "external_code").Unique(), + index.Fields("parent_id", "kind"), + } +} + +func (AdministrativeArea) Annotations() []entschema.Annotation { + return []entschema.Annotation{entsql.Annotation{Table: "administrative_areas"}} +} diff --git a/backend/ent/schema/locality.go b/backend/ent/schema/locality.go new file mode 100644 index 0000000..5d56fcd --- /dev/null +++ b/backend/ent/schema/locality.go @@ -0,0 +1,41 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect/entsql" + entschema "entgo.io/ent/schema" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "github.com/google/uuid" +) + +type Locality struct{ ent.Schema } + +func (Locality) Fields() []ent.Field { + return []ent.Field{ + field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable(), + field.String("slug").NotEmpty().MaxLen(100).Unique().Immutable(), + field.String("name").NotEmpty().MaxLen(160), + field.UUID("parent_parish_id", uuid.UUID{}).Immutable(), + field.String("source").NotEmpty().MaxLen(40).Immutable(), + field.String("source_element_id").NotEmpty().MaxLen(32).Unique().Immutable(), + field.String("source_version").NotEmpty().MaxLen(20).Immutable(), + field.Time("source_retrieved_at").Immutable(), + field.Float("latitude").Min(-90).Max(90).Immutable(), + field.Float("longitude").Min(-180).Max(180).Immutable(), + field.Bool("active").Default(true), + field.Time("created_at").Default(utcNow).Immutable(), + field.Time("updated_at").Default(utcNow).UpdateDefault(utcNow), + } +} + +func (Locality) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("parent_parish", AdministrativeArea.Type).Ref("localities").Field("parent_parish_id").Unique().Required().Immutable(), + edge.From("provider_profiles", ProviderProfile.Type).Ref("service_localities"), + } +} + +func (Locality) Annotations() []entschema.Annotation { + return []entschema.Annotation{entsql.Annotation{Table: "localities"}} +} diff --git a/backend/ent/schema/marketplace_reference_test.go b/backend/ent/schema/marketplace_reference_test.go new file mode 100644 index 0000000..e97ba27 --- /dev/null +++ b/backend/ent/schema/marketplace_reference_test.go @@ -0,0 +1,157 @@ +package schema_test + +import ( + "os" + "strings" + "testing" +) + +func TestMarketplaceReferenceSchemas(t *testing.T) { + t.Parallel() + + contracts := map[string][]string{ + "supportedlocale.go": { + "type SupportedLocale struct", + "field.String(\"id\")", + "MaxLen(10)", + "field.Bool(\"active\")", + "field.Int(\"sort_order\")", + "Table: \"supported_locales\"", + }, + "servicecategory.go": { + "type ServiceCategory struct", + "field.UUID(\"id\"", + "Default(uuid.New)", + "field.UUID(\"parent_id\"", + "Optional()", + "Nillable()", + "field.String(\"slug\")", + "Unique()", + "field.Bool(\"active\")", + "field.Int(\"sort_order\")", + "Table: \"service_categories\"", + }, + "servicecategorytranslation.go": { + "type ServiceCategoryTranslation struct", + "field.UUID(\"category_id\"", + "field.String(\"locale\")", + "field.String(\"name\")", + "field.String(\"description\")", + "field.ID(\"category_id\", \"locale\")", + "Table: \"service_category_translations\"", + }, + "spokenlanguage.go": { + "type SpokenLanguage struct", + "field.String(\"id\")", + "field.Bool(\"active\")", + "field.Int(\"sort_order\")", + "Table: \"spoken_languages\"", + }, + "spokenlanguagetranslation.go": { + "type SpokenLanguageTranslation struct", + "field.String(\"language_code\")", + "field.String(\"locale\")", + "field.String(\"name\")", + "field.ID(\"language_code\", \"locale\")", + "Table: \"spoken_language_translations\"", + }, + "administrativearea.go": { + "type AdministrativeArea struct", + "field.UUID(\"id\"", + "field.String(\"source\")", + "field.String(\"source_version\")", + "field.String(\"external_code\")", + "field.String(\"kind\")", + "field.String(\"name\")", + "field.UUID(\"parent_id\"", + "field.Bool(\"active\")", + "Table: \"administrative_areas\"", + }, + "locality.go": { + "type Locality struct", + "field.UUID(\"id\"", + "field.String(\"slug\")", + "field.String(\"name\")", + "field.UUID(\"parent_parish_id\"", + "field.String(\"source\")", + "field.String(\"source_element_id\")", + "field.Float(\"latitude\")", + "field.Float(\"longitude\")", + "field.Bool(\"active\")", + "Table: \"localities\"", + }, + "providerprofile.go": { + "type ProviderProfile struct", + "field.UUID(\"id\"", + "StorageKey(\"internal_user_id\")", + "field.String(\"display_name\")", + "field.String(\"provider_type\")", + "field.String(\"bio\")", + "field.UUID(\"primary_locality_id\"", + "field.Int(\"max_travel_distance_km\")", + "field.Bool(\"travels_to_customer\")", + "field.Bool(\"receives_customer\")", + "field.Bool(\"remote_services\")", + "Table: \"provider_profiles\"", + }, + "providerservicelocality.go": { + "type ProviderServiceLocality struct", + "field.UUID(\"internal_user_id\"", + "field.UUID(\"locality_id\"", + "field.ID(\"internal_user_id\", \"locality_id\")", + "Table: \"provider_service_localities\"", + }, + "providerspokenlanguage.go": { + "type ProviderSpokenLanguage struct", + "field.UUID(\"internal_user_id\"", + "field.String(\"language_code\")", + "field.ID(\"internal_user_id\", \"language_code\")", + "Table: \"provider_spoken_languages\"", + }, + } + + for path, required := range contracts { + path, required := path, required + t.Run(path, func(t *testing.T) { + t.Parallel() + contents, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read schema %s: %v", path, err) + } + schema := string(contents) + for _, requirement := range required { + if !strings.Contains(schema, requirement) { + t.Errorf("schema %s does not contain %q", path, requirement) + } + } + }) + } +} + +func TestProviderProfileSchemaExcludesPrivateAndDeferredFields(t *testing.T) { + t.Parallel() + + contents, err := os.ReadFile("providerprofile.go") + if err != nil { + t.Fatalf("read provider profile schema: %v", err) + } + schema := strings.ToLower(string(contents)) + for _, prohibited := range []string{ + "email", + "phone", + "whatsapp", + "address", + "clerk", + "token", + "session", + "verification", + "payment", + "listing", + "review", + "portfolio", + } { + if strings.Contains(schema, prohibited) { + t.Errorf("provider profile schema must not include %q", prohibited) + } + } +} diff --git a/backend/ent/schema/providerprofile.go b/backend/ent/schema/providerprofile.go new file mode 100644 index 0000000..0ddecc7 --- /dev/null +++ b/backend/ent/schema/providerprofile.go @@ -0,0 +1,39 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect/entsql" + entschema "entgo.io/ent/schema" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "github.com/google/uuid" +) + +type ProviderProfile struct{ ent.Schema } + +func (ProviderProfile) Fields() []ent.Field { + return []ent.Field{ + field.UUID("id", uuid.UUID{}).StorageKey("internal_user_id").Immutable(), + field.String("display_name").NotEmpty().MaxLen(100), + field.String("provider_type").NotEmpty().MaxLen(20), + field.String("bio").MaxLen(1000), + field.UUID("primary_locality_id", uuid.UUID{}), + field.Int("max_travel_distance_km").Min(0).Max(200), + field.Bool("travels_to_customer").Default(false), + field.Bool("receives_customer").Default(false), + field.Bool("remote_services").Default(false), + field.Time("created_at").Default(utcNow).Immutable(), + field.Time("updated_at").Default(utcNow).UpdateDefault(utcNow), + } +} + +func (ProviderProfile) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("service_localities", Locality.Type).Through("service_locality_links", ProviderServiceLocality.Type), + edge.To("spoken_languages", SpokenLanguage.Type).Through("spoken_language_links", ProviderSpokenLanguage.Type), + } +} + +func (ProviderProfile) Annotations() []entschema.Annotation { + return []entschema.Annotation{entsql.Annotation{Table: "provider_profiles"}} +} diff --git a/backend/ent/schema/providerservicelocality.go b/backend/ent/schema/providerservicelocality.go new file mode 100644 index 0000000..d17c53a --- /dev/null +++ b/backend/ent/schema/providerservicelocality.go @@ -0,0 +1,33 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect/entsql" + entschema "entgo.io/ent/schema" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "github.com/google/uuid" +) + +type ProviderServiceLocality struct{ ent.Schema } + +func (ProviderServiceLocality) Fields() []ent.Field { + return []ent.Field{ + field.UUID("internal_user_id", uuid.UUID{}).Immutable(), + field.UUID("locality_id", uuid.UUID{}).Immutable(), + } +} + +func (ProviderServiceLocality) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("profile", ProviderProfile.Type).Field("internal_user_id").Unique().Required().Immutable(), + edge.To("locality", Locality.Type).Field("locality_id").Unique().Required().Immutable(), + } +} + +func (ProviderServiceLocality) Annotations() []entschema.Annotation { + return []entschema.Annotation{ + field.ID("internal_user_id", "locality_id"), + entsql.Annotation{Table: "provider_service_localities"}, + } +} diff --git a/backend/ent/schema/providerspokenlanguage.go b/backend/ent/schema/providerspokenlanguage.go new file mode 100644 index 0000000..fc8a80e --- /dev/null +++ b/backend/ent/schema/providerspokenlanguage.go @@ -0,0 +1,33 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect/entsql" + entschema "entgo.io/ent/schema" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "github.com/google/uuid" +) + +type ProviderSpokenLanguage struct{ ent.Schema } + +func (ProviderSpokenLanguage) Fields() []ent.Field { + return []ent.Field{ + field.UUID("internal_user_id", uuid.UUID{}).Immutable(), + field.String("language_code").NotEmpty().MaxLen(10).Immutable(), + } +} + +func (ProviderSpokenLanguage) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("profile", ProviderProfile.Type).Field("internal_user_id").Unique().Required().Immutable(), + edge.To("language", SpokenLanguage.Type).Field("language_code").Unique().Required().Immutable(), + } +} + +func (ProviderSpokenLanguage) Annotations() []entschema.Annotation { + return []entschema.Annotation{ + field.ID("internal_user_id", "language_code"), + entsql.Annotation{Table: "provider_spoken_languages"}, + } +} diff --git a/backend/ent/schema/servicecategory.go b/backend/ent/schema/servicecategory.go new file mode 100644 index 0000000..af5c788 --- /dev/null +++ b/backend/ent/schema/servicecategory.go @@ -0,0 +1,41 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect/entsql" + entschema "entgo.io/ent/schema" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "github.com/google/uuid" +) + +type ServiceCategory struct{ ent.Schema } + +func (ServiceCategory) Fields() []ent.Field { + return []ent.Field{ + field.UUID("id", uuid.UUID{}).Default(uuid.New).Immutable(), + field.UUID("parent_id", uuid.UUID{}).Optional().Nillable(), + field.String("slug").NotEmpty().MaxLen(80).Unique().Immutable(), + field.Bool("active").Default(true), + field.Int("sort_order").NonNegative(), + field.Time("created_at").Default(utcNow).Immutable(), + field.Time("updated_at").Default(utcNow).UpdateDefault(utcNow), + } +} + +func (ServiceCategory) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("children", ServiceCategory.Type), + edge.From("parent", ServiceCategory.Type).Ref("children").Field("parent_id").Unique(), + edge.To("localized_in", SupportedLocale.Type).Through("translations", ServiceCategoryTranslation.Type), + } +} + +func (ServiceCategory) Indexes() []ent.Index { + return []ent.Index{index.Fields("parent_id", "sort_order")} +} + +func (ServiceCategory) Annotations() []entschema.Annotation { + return []entschema.Annotation{entsql.Annotation{Table: "service_categories"}} +} diff --git a/backend/ent/schema/servicecategorytranslation.go b/backend/ent/schema/servicecategorytranslation.go new file mode 100644 index 0000000..5be87c6 --- /dev/null +++ b/backend/ent/schema/servicecategorytranslation.go @@ -0,0 +1,35 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect/entsql" + entschema "entgo.io/ent/schema" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "github.com/google/uuid" +) + +type ServiceCategoryTranslation struct{ ent.Schema } + +func (ServiceCategoryTranslation) Fields() []ent.Field { + return []ent.Field{ + field.UUID("category_id", uuid.UUID{}).Immutable(), + field.String("locale").NotEmpty().MaxLen(10).Immutable(), + field.String("name").NotEmpty().MaxLen(120), + field.String("description").MaxLen(500).Optional().Nillable(), + } +} + +func (ServiceCategoryTranslation) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("category", ServiceCategory.Type).Field("category_id").Unique().Required().Immutable(), + edge.To("locale_record", SupportedLocale.Type).Field("locale").Unique().Required().Immutable(), + } +} + +func (ServiceCategoryTranslation) Annotations() []entschema.Annotation { + return []entschema.Annotation{ + field.ID("category_id", "locale"), + entsql.Annotation{Table: "service_category_translations"}, + } +} diff --git a/backend/ent/schema/spokenlanguage.go b/backend/ent/schema/spokenlanguage.go new file mode 100644 index 0000000..d9e22cd --- /dev/null +++ b/backend/ent/schema/spokenlanguage.go @@ -0,0 +1,30 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect/entsql" + entschema "entgo.io/ent/schema" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" +) + +type SpokenLanguage struct{ ent.Schema } + +func (SpokenLanguage) Fields() []ent.Field { + return []ent.Field{ + field.String("id").NotEmpty().MaxLen(10).Immutable(), + field.Bool("active").Default(true), + field.Int("sort_order").NonNegative(), + } +} + +func (SpokenLanguage) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("localized_in", SupportedLocale.Type).Through("translations", SpokenLanguageTranslation.Type), + edge.From("provider_profiles", ProviderProfile.Type).Ref("spoken_languages"), + } +} + +func (SpokenLanguage) Annotations() []entschema.Annotation { + return []entschema.Annotation{entsql.Annotation{Table: "spoken_languages"}} +} diff --git a/backend/ent/schema/spokenlanguagetranslation.go b/backend/ent/schema/spokenlanguagetranslation.go new file mode 100644 index 0000000..4e576d6 --- /dev/null +++ b/backend/ent/schema/spokenlanguagetranslation.go @@ -0,0 +1,33 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect/entsql" + entschema "entgo.io/ent/schema" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" +) + +type SpokenLanguageTranslation struct{ ent.Schema } + +func (SpokenLanguageTranslation) Fields() []ent.Field { + return []ent.Field{ + field.String("language_code").NotEmpty().MaxLen(10).Immutable(), + field.String("locale").NotEmpty().MaxLen(10).Immutable(), + field.String("name").NotEmpty().MaxLen(80), + } +} + +func (SpokenLanguageTranslation) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("language", SpokenLanguage.Type).Field("language_code").Unique().Required().Immutable(), + edge.To("locale_record", SupportedLocale.Type).Field("locale").Unique().Required().Immutable(), + } +} + +func (SpokenLanguageTranslation) Annotations() []entschema.Annotation { + return []entschema.Annotation{ + field.ID("language_code", "locale"), + entsql.Annotation{Table: "spoken_language_translations"}, + } +} diff --git a/backend/ent/schema/supportedlocale.go b/backend/ent/schema/supportedlocale.go new file mode 100644 index 0000000..d0fb152 --- /dev/null +++ b/backend/ent/schema/supportedlocale.go @@ -0,0 +1,30 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect/entsql" + entschema "entgo.io/ent/schema" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" +) + +type SupportedLocale struct{ ent.Schema } + +func (SupportedLocale) Fields() []ent.Field { + return []ent.Field{ + field.String("id").NotEmpty().MaxLen(10).Immutable(), + field.Bool("active").Default(true), + field.Int("sort_order").NonNegative(), + } +} + +func (SupportedLocale) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("translated_categories", ServiceCategory.Type).Ref("localized_in"), + edge.From("translated_languages", SpokenLanguage.Type).Ref("localized_in"), + } +} + +func (SupportedLocale) Annotations() []entschema.Annotation { + return []entschema.Annotation{entsql.Annotation{Table: "supported_locales"}} +} diff --git a/backend/ent/servicecategory.go b/backend/ent/servicecategory.go new file mode 100644 index 0000000..2b010ab --- /dev/null +++ b/backend/ent/servicecategory.go @@ -0,0 +1,247 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/google/uuid" +) + +// ServiceCategory is the model entity for the ServiceCategory schema. +type ServiceCategory struct { + config `json:"-"` + // ID of the ent. + ID uuid.UUID `json:"id,omitempty"` + // ParentID holds the value of the "parent_id" field. + ParentID *uuid.UUID `json:"parent_id,omitempty"` + // Slug holds the value of the "slug" field. + Slug string `json:"slug,omitempty"` + // Active holds the value of the "active" field. + Active bool `json:"active,omitempty"` + // SortOrder holds the value of the "sort_order" field. + SortOrder int `json:"sort_order,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the ServiceCategoryQuery when eager-loading is set. + Edges ServiceCategoryEdges `json:"edges"` + selectValues sql.SelectValues +} + +// ServiceCategoryEdges holds the relations/edges for other nodes in the graph. +type ServiceCategoryEdges struct { + // Children holds the value of the children edge. + Children []*ServiceCategory `json:"children,omitempty"` + // Parent holds the value of the parent edge. + Parent *ServiceCategory `json:"parent,omitempty"` + // LocalizedIn holds the value of the localized_in edge. + LocalizedIn []*SupportedLocale `json:"localized_in,omitempty"` + // Translations holds the value of the translations edge. + Translations []*ServiceCategoryTranslation `json:"translations,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [4]bool +} + +// ChildrenOrErr returns the Children value or an error if the edge +// was not loaded in eager-loading. +func (e ServiceCategoryEdges) ChildrenOrErr() ([]*ServiceCategory, error) { + if e.loadedTypes[0] { + return e.Children, nil + } + return nil, &NotLoadedError{edge: "children"} +} + +// ParentOrErr returns the Parent value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e ServiceCategoryEdges) ParentOrErr() (*ServiceCategory, error) { + if e.Parent != nil { + return e.Parent, nil + } else if e.loadedTypes[1] { + return nil, &NotFoundError{label: servicecategory.Label} + } + return nil, &NotLoadedError{edge: "parent"} +} + +// LocalizedInOrErr returns the LocalizedIn value or an error if the edge +// was not loaded in eager-loading. +func (e ServiceCategoryEdges) LocalizedInOrErr() ([]*SupportedLocale, error) { + if e.loadedTypes[2] { + return e.LocalizedIn, nil + } + return nil, &NotLoadedError{edge: "localized_in"} +} + +// TranslationsOrErr returns the Translations value or an error if the edge +// was not loaded in eager-loading. +func (e ServiceCategoryEdges) TranslationsOrErr() ([]*ServiceCategoryTranslation, error) { + if e.loadedTypes[3] { + return e.Translations, nil + } + return nil, &NotLoadedError{edge: "translations"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*ServiceCategory) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case servicecategory.FieldParentID: + values[i] = &sql.NullScanner{S: new(uuid.UUID)} + case servicecategory.FieldActive: + values[i] = new(sql.NullBool) + case servicecategory.FieldSortOrder: + values[i] = new(sql.NullInt64) + case servicecategory.FieldSlug: + values[i] = new(sql.NullString) + case servicecategory.FieldCreatedAt, servicecategory.FieldUpdatedAt: + values[i] = new(sql.NullTime) + case servicecategory.FieldID: + values[i] = new(uuid.UUID) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the ServiceCategory fields. +func (_m *ServiceCategory) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case servicecategory.FieldID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value != nil { + _m.ID = *value + } + case servicecategory.FieldParentID: + if value, ok := values[i].(*sql.NullScanner); !ok { + return fmt.Errorf("unexpected type %T for field parent_id", values[i]) + } else if value.Valid { + _m.ParentID = new(uuid.UUID) + *_m.ParentID = *value.S.(*uuid.UUID) + } + case servicecategory.FieldSlug: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field slug", values[i]) + } else if value.Valid { + _m.Slug = value.String + } + case servicecategory.FieldActive: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field active", values[i]) + } else if value.Valid { + _m.Active = value.Bool + } + case servicecategory.FieldSortOrder: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field sort_order", values[i]) + } else if value.Valid { + _m.SortOrder = int(value.Int64) + } + case servicecategory.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + _m.CreatedAt = value.Time + } + case servicecategory.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + _m.UpdatedAt = value.Time + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the ServiceCategory. +// This includes values selected through modifiers, order, etc. +func (_m *ServiceCategory) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QueryChildren queries the "children" edge of the ServiceCategory entity. +func (_m *ServiceCategory) QueryChildren() *ServiceCategoryQuery { + return NewServiceCategoryClient(_m.config).QueryChildren(_m) +} + +// QueryParent queries the "parent" edge of the ServiceCategory entity. +func (_m *ServiceCategory) QueryParent() *ServiceCategoryQuery { + return NewServiceCategoryClient(_m.config).QueryParent(_m) +} + +// QueryLocalizedIn queries the "localized_in" edge of the ServiceCategory entity. +func (_m *ServiceCategory) QueryLocalizedIn() *SupportedLocaleQuery { + return NewServiceCategoryClient(_m.config).QueryLocalizedIn(_m) +} + +// QueryTranslations queries the "translations" edge of the ServiceCategory entity. +func (_m *ServiceCategory) QueryTranslations() *ServiceCategoryTranslationQuery { + return NewServiceCategoryClient(_m.config).QueryTranslations(_m) +} + +// Update returns a builder for updating this ServiceCategory. +// Note that you need to call ServiceCategory.Unwrap() before calling this method if this ServiceCategory +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *ServiceCategory) Update() *ServiceCategoryUpdateOne { + return NewServiceCategoryClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the ServiceCategory entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *ServiceCategory) Unwrap() *ServiceCategory { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: ServiceCategory is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *ServiceCategory) String() string { + var builder strings.Builder + builder.WriteString("ServiceCategory(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + if v := _m.ParentID; v != nil { + builder.WriteString("parent_id=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") + builder.WriteString("slug=") + builder.WriteString(_m.Slug) + builder.WriteString(", ") + builder.WriteString("active=") + builder.WriteString(fmt.Sprintf("%v", _m.Active)) + builder.WriteString(", ") + builder.WriteString("sort_order=") + builder.WriteString(fmt.Sprintf("%v", _m.SortOrder)) + builder.WriteString(", ") + builder.WriteString("created_at=") + builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(_m.UpdatedAt.Format(time.ANSIC)) + builder.WriteByte(')') + return builder.String() +} + +// ServiceCategories is a parsable slice of ServiceCategory. +type ServiceCategories []*ServiceCategory diff --git a/backend/ent/servicecategory/servicecategory.go b/backend/ent/servicecategory/servicecategory.go new file mode 100644 index 0000000..1660e34 --- /dev/null +++ b/backend/ent/servicecategory/servicecategory.go @@ -0,0 +1,219 @@ +// Code generated by ent, DO NOT EDIT. + +package servicecategory + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/google/uuid" +) + +const ( + // Label holds the string label denoting the servicecategory type in the database. + Label = "service_category" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldParentID holds the string denoting the parent_id field in the database. + FieldParentID = "parent_id" + // FieldSlug holds the string denoting the slug field in the database. + FieldSlug = "slug" + // FieldActive holds the string denoting the active field in the database. + FieldActive = "active" + // FieldSortOrder holds the string denoting the sort_order field in the database. + FieldSortOrder = "sort_order" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // EdgeChildren holds the string denoting the children edge name in mutations. + EdgeChildren = "children" + // EdgeParent holds the string denoting the parent edge name in mutations. + EdgeParent = "parent" + // EdgeLocalizedIn holds the string denoting the localized_in edge name in mutations. + EdgeLocalizedIn = "localized_in" + // EdgeTranslations holds the string denoting the translations edge name in mutations. + EdgeTranslations = "translations" + // Table holds the table name of the servicecategory in the database. + Table = "service_categories" + // ChildrenTable is the table that holds the children relation/edge. + ChildrenTable = "service_categories" + // ChildrenColumn is the table column denoting the children relation/edge. + ChildrenColumn = "parent_id" + // ParentTable is the table that holds the parent relation/edge. + ParentTable = "service_categories" + // ParentColumn is the table column denoting the parent relation/edge. + ParentColumn = "parent_id" + // LocalizedInTable is the table that holds the localized_in relation/edge. The primary key declared below. + LocalizedInTable = "service_category_translations" + // LocalizedInInverseTable is the table name for the SupportedLocale entity. + // It exists in this package in order to avoid circular dependency with the "supportedlocale" package. + LocalizedInInverseTable = "supported_locales" + // TranslationsTable is the table that holds the translations relation/edge. + TranslationsTable = "service_category_translations" + // TranslationsInverseTable is the table name for the ServiceCategoryTranslation entity. + // It exists in this package in order to avoid circular dependency with the "servicecategorytranslation" package. + TranslationsInverseTable = "service_category_translations" + // TranslationsColumn is the table column denoting the translations relation/edge. + TranslationsColumn = "category_id" +) + +// Columns holds all SQL columns for servicecategory fields. +var Columns = []string{ + FieldID, + FieldParentID, + FieldSlug, + FieldActive, + FieldSortOrder, + FieldCreatedAt, + FieldUpdatedAt, +} + +var ( + // LocalizedInPrimaryKey and LocalizedInColumn2 are the table columns denoting the + // primary key for the localized_in relation (M2M). + LocalizedInPrimaryKey = []string{"category_id", "locale"} +) + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // SlugValidator is a validator for the "slug" field. It is called by the builders before save. + SlugValidator func(string) error + // DefaultActive holds the default value on creation for the "active" field. + DefaultActive bool + // SortOrderValidator is a validator for the "sort_order" field. It is called by the builders before save. + SortOrderValidator func(int) error + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() uuid.UUID +) + +// OrderOption defines the ordering options for the ServiceCategory queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByParentID orders the results by the parent_id field. +func ByParentID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldParentID, opts...).ToFunc() +} + +// BySlug orders the results by the slug field. +func BySlug(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSlug, opts...).ToFunc() +} + +// ByActive orders the results by the active field. +func ByActive(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldActive, opts...).ToFunc() +} + +// BySortOrder orders the results by the sort_order field. +func BySortOrder(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSortOrder, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByChildrenCount orders the results by children count. +func ByChildrenCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newChildrenStep(), opts...) + } +} + +// ByChildren orders the results by children terms. +func ByChildren(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newChildrenStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByParentField orders the results by parent field. +func ByParentField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newParentStep(), sql.OrderByField(field, opts...)) + } +} + +// ByLocalizedInCount orders the results by localized_in count. +func ByLocalizedInCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newLocalizedInStep(), opts...) + } +} + +// ByLocalizedIn orders the results by localized_in terms. +func ByLocalizedIn(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newLocalizedInStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByTranslationsCount orders the results by translations count. +func ByTranslationsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newTranslationsStep(), opts...) + } +} + +// ByTranslations orders the results by translations terms. +func ByTranslations(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newTranslationsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} +func newChildrenStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn), + ) +} +func newParentStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, ParentTable, ParentColumn), + ) +} +func newLocalizedInStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(LocalizedInInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, LocalizedInTable, LocalizedInPrimaryKey...), + ) +} +func newTranslationsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(TranslationsInverseTable, TranslationsColumn), + sqlgraph.Edge(sqlgraph.O2M, true, TranslationsTable, TranslationsColumn), + ) +} diff --git a/backend/ent/servicecategory/where.go b/backend/ent/servicecategory/where.go new file mode 100644 index 0000000..bf77828 --- /dev/null +++ b/backend/ent/servicecategory/where.go @@ -0,0 +1,419 @@ +// Code generated by ent, DO NOT EDIT. + +package servicecategory + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +// ID filters vertices based on their ID field. +func ID(id uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldLTE(FieldID, id)) +} + +// ParentID applies equality check predicate on the "parent_id" field. It's identical to ParentIDEQ. +func ParentID(v uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldParentID, v)) +} + +// Slug applies equality check predicate on the "slug" field. It's identical to SlugEQ. +func Slug(v string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldSlug, v)) +} + +// Active applies equality check predicate on the "active" field. It's identical to ActiveEQ. +func Active(v bool) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldActive, v)) +} + +// SortOrder applies equality check predicate on the "sort_order" field. It's identical to SortOrderEQ. +func SortOrder(v int) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldSortOrder, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// ParentIDEQ applies the EQ predicate on the "parent_id" field. +func ParentIDEQ(v uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldParentID, v)) +} + +// ParentIDNEQ applies the NEQ predicate on the "parent_id" field. +func ParentIDNEQ(v uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNEQ(FieldParentID, v)) +} + +// ParentIDIn applies the In predicate on the "parent_id" field. +func ParentIDIn(vs ...uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldIn(FieldParentID, vs...)) +} + +// ParentIDNotIn applies the NotIn predicate on the "parent_id" field. +func ParentIDNotIn(vs ...uuid.UUID) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNotIn(FieldParentID, vs...)) +} + +// ParentIDIsNil applies the IsNil predicate on the "parent_id" field. +func ParentIDIsNil() predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldIsNull(FieldParentID)) +} + +// ParentIDNotNil applies the NotNil predicate on the "parent_id" field. +func ParentIDNotNil() predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNotNull(FieldParentID)) +} + +// SlugEQ applies the EQ predicate on the "slug" field. +func SlugEQ(v string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldSlug, v)) +} + +// SlugNEQ applies the NEQ predicate on the "slug" field. +func SlugNEQ(v string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNEQ(FieldSlug, v)) +} + +// SlugIn applies the In predicate on the "slug" field. +func SlugIn(vs ...string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldIn(FieldSlug, vs...)) +} + +// SlugNotIn applies the NotIn predicate on the "slug" field. +func SlugNotIn(vs ...string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNotIn(FieldSlug, vs...)) +} + +// SlugGT applies the GT predicate on the "slug" field. +func SlugGT(v string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldGT(FieldSlug, v)) +} + +// SlugGTE applies the GTE predicate on the "slug" field. +func SlugGTE(v string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldGTE(FieldSlug, v)) +} + +// SlugLT applies the LT predicate on the "slug" field. +func SlugLT(v string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldLT(FieldSlug, v)) +} + +// SlugLTE applies the LTE predicate on the "slug" field. +func SlugLTE(v string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldLTE(FieldSlug, v)) +} + +// SlugContains applies the Contains predicate on the "slug" field. +func SlugContains(v string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldContains(FieldSlug, v)) +} + +// SlugHasPrefix applies the HasPrefix predicate on the "slug" field. +func SlugHasPrefix(v string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldHasPrefix(FieldSlug, v)) +} + +// SlugHasSuffix applies the HasSuffix predicate on the "slug" field. +func SlugHasSuffix(v string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldHasSuffix(FieldSlug, v)) +} + +// SlugEqualFold applies the EqualFold predicate on the "slug" field. +func SlugEqualFold(v string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEqualFold(FieldSlug, v)) +} + +// SlugContainsFold applies the ContainsFold predicate on the "slug" field. +func SlugContainsFold(v string) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldContainsFold(FieldSlug, v)) +} + +// ActiveEQ applies the EQ predicate on the "active" field. +func ActiveEQ(v bool) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldActive, v)) +} + +// ActiveNEQ applies the NEQ predicate on the "active" field. +func ActiveNEQ(v bool) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNEQ(FieldActive, v)) +} + +// SortOrderEQ applies the EQ predicate on the "sort_order" field. +func SortOrderEQ(v int) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldSortOrder, v)) +} + +// SortOrderNEQ applies the NEQ predicate on the "sort_order" field. +func SortOrderNEQ(v int) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNEQ(FieldSortOrder, v)) +} + +// SortOrderIn applies the In predicate on the "sort_order" field. +func SortOrderIn(vs ...int) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldIn(FieldSortOrder, vs...)) +} + +// SortOrderNotIn applies the NotIn predicate on the "sort_order" field. +func SortOrderNotIn(vs ...int) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNotIn(FieldSortOrder, vs...)) +} + +// SortOrderGT applies the GT predicate on the "sort_order" field. +func SortOrderGT(v int) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldGT(FieldSortOrder, v)) +} + +// SortOrderGTE applies the GTE predicate on the "sort_order" field. +func SortOrderGTE(v int) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldGTE(FieldSortOrder, v)) +} + +// SortOrderLT applies the LT predicate on the "sort_order" field. +func SortOrderLT(v int) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldLT(FieldSortOrder, v)) +} + +// SortOrderLTE applies the LTE predicate on the "sort_order" field. +func SortOrderLTE(v int) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldLTE(FieldSortOrder, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// HasChildren applies the HasEdge predicate on the "children" edge. +func HasChildren() predicate.ServiceCategory { + return predicate.ServiceCategory(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasChildrenWith applies the HasEdge predicate on the "children" edge with a given conditions (other predicates). +func HasChildrenWith(preds ...predicate.ServiceCategory) predicate.ServiceCategory { + return predicate.ServiceCategory(func(s *sql.Selector) { + step := newChildrenStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasParent applies the HasEdge predicate on the "parent" edge. +func HasParent() predicate.ServiceCategory { + return predicate.ServiceCategory(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, ParentTable, ParentColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasParentWith applies the HasEdge predicate on the "parent" edge with a given conditions (other predicates). +func HasParentWith(preds ...predicate.ServiceCategory) predicate.ServiceCategory { + return predicate.ServiceCategory(func(s *sql.Selector) { + step := newParentStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasLocalizedIn applies the HasEdge predicate on the "localized_in" edge. +func HasLocalizedIn() predicate.ServiceCategory { + return predicate.ServiceCategory(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, LocalizedInTable, LocalizedInPrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasLocalizedInWith applies the HasEdge predicate on the "localized_in" edge with a given conditions (other predicates). +func HasLocalizedInWith(preds ...predicate.SupportedLocale) predicate.ServiceCategory { + return predicate.ServiceCategory(func(s *sql.Selector) { + step := newLocalizedInStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasTranslations applies the HasEdge predicate on the "translations" edge. +func HasTranslations() predicate.ServiceCategory { + return predicate.ServiceCategory(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, TranslationsTable, TranslationsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasTranslationsWith applies the HasEdge predicate on the "translations" edge with a given conditions (other predicates). +func HasTranslationsWith(preds ...predicate.ServiceCategoryTranslation) predicate.ServiceCategory { + return predicate.ServiceCategory(func(s *sql.Selector) { + step := newTranslationsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.ServiceCategory) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.ServiceCategory) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.ServiceCategory) predicate.ServiceCategory { + return predicate.ServiceCategory(sql.NotPredicates(p)) +} diff --git a/backend/ent/servicecategory_create.go b/backend/ent/servicecategory_create.go new file mode 100644 index 0000000..fc9e49b --- /dev/null +++ b/backend/ent/servicecategory_create.go @@ -0,0 +1,411 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" + "github.com/google/uuid" +) + +// ServiceCategoryCreate is the builder for creating a ServiceCategory entity. +type ServiceCategoryCreate struct { + config + mutation *ServiceCategoryMutation + hooks []Hook +} + +// SetParentID sets the "parent_id" field. +func (_c *ServiceCategoryCreate) SetParentID(v uuid.UUID) *ServiceCategoryCreate { + _c.mutation.SetParentID(v) + return _c +} + +// SetNillableParentID sets the "parent_id" field if the given value is not nil. +func (_c *ServiceCategoryCreate) SetNillableParentID(v *uuid.UUID) *ServiceCategoryCreate { + if v != nil { + _c.SetParentID(*v) + } + return _c +} + +// SetSlug sets the "slug" field. +func (_c *ServiceCategoryCreate) SetSlug(v string) *ServiceCategoryCreate { + _c.mutation.SetSlug(v) + return _c +} + +// SetActive sets the "active" field. +func (_c *ServiceCategoryCreate) SetActive(v bool) *ServiceCategoryCreate { + _c.mutation.SetActive(v) + return _c +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_c *ServiceCategoryCreate) SetNillableActive(v *bool) *ServiceCategoryCreate { + if v != nil { + _c.SetActive(*v) + } + return _c +} + +// SetSortOrder sets the "sort_order" field. +func (_c *ServiceCategoryCreate) SetSortOrder(v int) *ServiceCategoryCreate { + _c.mutation.SetSortOrder(v) + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *ServiceCategoryCreate) SetCreatedAt(v time.Time) *ServiceCategoryCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *ServiceCategoryCreate) SetNillableCreatedAt(v *time.Time) *ServiceCategoryCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *ServiceCategoryCreate) SetUpdatedAt(v time.Time) *ServiceCategoryCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (_c *ServiceCategoryCreate) SetNillableUpdatedAt(v *time.Time) *ServiceCategoryCreate { + if v != nil { + _c.SetUpdatedAt(*v) + } + return _c +} + +// SetID sets the "id" field. +func (_c *ServiceCategoryCreate) SetID(v uuid.UUID) *ServiceCategoryCreate { + _c.mutation.SetID(v) + return _c +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (_c *ServiceCategoryCreate) SetNillableID(v *uuid.UUID) *ServiceCategoryCreate { + if v != nil { + _c.SetID(*v) + } + return _c +} + +// AddChildIDs adds the "children" edge to the ServiceCategory entity by IDs. +func (_c *ServiceCategoryCreate) AddChildIDs(ids ...uuid.UUID) *ServiceCategoryCreate { + _c.mutation.AddChildIDs(ids...) + return _c +} + +// AddChildren adds the "children" edges to the ServiceCategory entity. +func (_c *ServiceCategoryCreate) AddChildren(v ...*ServiceCategory) *ServiceCategoryCreate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddChildIDs(ids...) +} + +// SetParent sets the "parent" edge to the ServiceCategory entity. +func (_c *ServiceCategoryCreate) SetParent(v *ServiceCategory) *ServiceCategoryCreate { + return _c.SetParentID(v.ID) +} + +// AddLocalizedInIDs adds the "localized_in" edge to the SupportedLocale entity by IDs. +func (_c *ServiceCategoryCreate) AddLocalizedInIDs(ids ...string) *ServiceCategoryCreate { + _c.mutation.AddLocalizedInIDs(ids...) + return _c +} + +// AddLocalizedIn adds the "localized_in" edges to the SupportedLocale entity. +func (_c *ServiceCategoryCreate) AddLocalizedIn(v ...*SupportedLocale) *ServiceCategoryCreate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddLocalizedInIDs(ids...) +} + +// Mutation returns the ServiceCategoryMutation object of the builder. +func (_c *ServiceCategoryCreate) Mutation() *ServiceCategoryMutation { + return _c.mutation +} + +// Save creates the ServiceCategory in the database. +func (_c *ServiceCategoryCreate) Save(ctx context.Context) (*ServiceCategory, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *ServiceCategoryCreate) SaveX(ctx context.Context) *ServiceCategory { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *ServiceCategoryCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *ServiceCategoryCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *ServiceCategoryCreate) defaults() { + if _, ok := _c.mutation.Active(); !ok { + v := servicecategory.DefaultActive + _c.mutation.SetActive(v) + } + if _, ok := _c.mutation.CreatedAt(); !ok { + v := servicecategory.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + v := servicecategory.DefaultUpdatedAt() + _c.mutation.SetUpdatedAt(v) + } + if _, ok := _c.mutation.ID(); !ok { + v := servicecategory.DefaultID() + _c.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *ServiceCategoryCreate) check() error { + if _, ok := _c.mutation.Slug(); !ok { + return &ValidationError{Name: "slug", err: errors.New(`ent: missing required field "ServiceCategory.slug"`)} + } + if v, ok := _c.mutation.Slug(); ok { + if err := servicecategory.SlugValidator(v); err != nil { + return &ValidationError{Name: "slug", err: fmt.Errorf(`ent: validator failed for field "ServiceCategory.slug": %w`, err)} + } + } + if _, ok := _c.mutation.Active(); !ok { + return &ValidationError{Name: "active", err: errors.New(`ent: missing required field "ServiceCategory.active"`)} + } + if _, ok := _c.mutation.SortOrder(); !ok { + return &ValidationError{Name: "sort_order", err: errors.New(`ent: missing required field "ServiceCategory.sort_order"`)} + } + if v, ok := _c.mutation.SortOrder(); ok { + if err := servicecategory.SortOrderValidator(v); err != nil { + return &ValidationError{Name: "sort_order", err: fmt.Errorf(`ent: validator failed for field "ServiceCategory.sort_order": %w`, err)} + } + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "ServiceCategory.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "ServiceCategory.updated_at"`)} + } + return nil +} + +func (_c *ServiceCategoryCreate) sqlSave(ctx context.Context) (*ServiceCategory, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(*uuid.UUID); ok { + _node.ID = *id + } else if err := _node.ID.Scan(_spec.ID.Value); err != nil { + return nil, err + } + } + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *ServiceCategoryCreate) createSpec() (*ServiceCategory, *sqlgraph.CreateSpec) { + var ( + _node = &ServiceCategory{config: _c.config} + _spec = sqlgraph.NewCreateSpec(servicecategory.Table, sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID)) + ) + if id, ok := _c.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = &id + } + if value, ok := _c.mutation.Slug(); ok { + _spec.SetField(servicecategory.FieldSlug, field.TypeString, value) + _node.Slug = value + } + if value, ok := _c.mutation.Active(); ok { + _spec.SetField(servicecategory.FieldActive, field.TypeBool, value) + _node.Active = value + } + if value, ok := _c.mutation.SortOrder(); ok { + _spec.SetField(servicecategory.FieldSortOrder, field.TypeInt, value) + _node.SortOrder = value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(servicecategory.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(servicecategory.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if nodes := _c.mutation.ChildrenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: servicecategory.ChildrenTable, + Columns: []string{servicecategory.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.ParentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: servicecategory.ParentTable, + Columns: []string{servicecategory.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.ParentID = &nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.LocalizedInIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: servicecategory.LocalizedInTable, + Columns: servicecategory.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// ServiceCategoryCreateBulk is the builder for creating many ServiceCategory entities in bulk. +type ServiceCategoryCreateBulk struct { + config + err error + builders []*ServiceCategoryCreate +} + +// Save creates the ServiceCategory entities in the database. +func (_c *ServiceCategoryCreateBulk) Save(ctx context.Context) ([]*ServiceCategory, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*ServiceCategory, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*ServiceCategoryMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *ServiceCategoryCreateBulk) SaveX(ctx context.Context) []*ServiceCategory { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *ServiceCategoryCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *ServiceCategoryCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/servicecategory_delete.go b/backend/ent/servicecategory_delete.go new file mode 100644 index 0000000..c573593 --- /dev/null +++ b/backend/ent/servicecategory_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" +) + +// ServiceCategoryDelete is the builder for deleting a ServiceCategory entity. +type ServiceCategoryDelete struct { + config + hooks []Hook + mutation *ServiceCategoryMutation +} + +// Where appends a list predicates to the ServiceCategoryDelete builder. +func (_d *ServiceCategoryDelete) Where(ps ...predicate.ServiceCategory) *ServiceCategoryDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *ServiceCategoryDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *ServiceCategoryDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *ServiceCategoryDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(servicecategory.Table, sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// ServiceCategoryDeleteOne is the builder for deleting a single ServiceCategory entity. +type ServiceCategoryDeleteOne struct { + _d *ServiceCategoryDelete +} + +// Where appends a list predicates to the ServiceCategoryDelete builder. +func (_d *ServiceCategoryDeleteOne) Where(ps ...predicate.ServiceCategory) *ServiceCategoryDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *ServiceCategoryDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{servicecategory.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *ServiceCategoryDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/servicecategory_query.go b/backend/ent/servicecategory_query.go new file mode 100644 index 0000000..9a33778 --- /dev/null +++ b/backend/ent/servicecategory_query.go @@ -0,0 +1,867 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "database/sql/driver" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategorytranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" + "github.com/google/uuid" +) + +// ServiceCategoryQuery is the builder for querying ServiceCategory entities. +type ServiceCategoryQuery struct { + config + ctx *QueryContext + order []servicecategory.OrderOption + inters []Interceptor + predicates []predicate.ServiceCategory + withChildren *ServiceCategoryQuery + withParent *ServiceCategoryQuery + withLocalizedIn *SupportedLocaleQuery + withTranslations *ServiceCategoryTranslationQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the ServiceCategoryQuery builder. +func (_q *ServiceCategoryQuery) Where(ps ...predicate.ServiceCategory) *ServiceCategoryQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *ServiceCategoryQuery) Limit(limit int) *ServiceCategoryQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *ServiceCategoryQuery) Offset(offset int) *ServiceCategoryQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *ServiceCategoryQuery) Unique(unique bool) *ServiceCategoryQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *ServiceCategoryQuery) Order(o ...servicecategory.OrderOption) *ServiceCategoryQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QueryChildren chains the current query on the "children" edge. +func (_q *ServiceCategoryQuery) QueryChildren() *ServiceCategoryQuery { + query := (&ServiceCategoryClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(servicecategory.Table, servicecategory.FieldID, selector), + sqlgraph.To(servicecategory.Table, servicecategory.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, servicecategory.ChildrenTable, servicecategory.ChildrenColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryParent chains the current query on the "parent" edge. +func (_q *ServiceCategoryQuery) QueryParent() *ServiceCategoryQuery { + query := (&ServiceCategoryClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(servicecategory.Table, servicecategory.FieldID, selector), + sqlgraph.To(servicecategory.Table, servicecategory.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, servicecategory.ParentTable, servicecategory.ParentColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryLocalizedIn chains the current query on the "localized_in" edge. +func (_q *ServiceCategoryQuery) QueryLocalizedIn() *SupportedLocaleQuery { + query := (&SupportedLocaleClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(servicecategory.Table, servicecategory.FieldID, selector), + sqlgraph.To(supportedlocale.Table, supportedlocale.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, servicecategory.LocalizedInTable, servicecategory.LocalizedInPrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryTranslations chains the current query on the "translations" edge. +func (_q *ServiceCategoryQuery) QueryTranslations() *ServiceCategoryTranslationQuery { + query := (&ServiceCategoryTranslationClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(servicecategory.Table, servicecategory.FieldID, selector), + sqlgraph.To(servicecategorytranslation.Table, servicecategorytranslation.CategoryColumn), + sqlgraph.Edge(sqlgraph.O2M, true, servicecategory.TranslationsTable, servicecategory.TranslationsColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first ServiceCategory entity from the query. +// Returns a *NotFoundError when no ServiceCategory was found. +func (_q *ServiceCategoryQuery) First(ctx context.Context) (*ServiceCategory, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{servicecategory.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *ServiceCategoryQuery) FirstX(ctx context.Context) *ServiceCategory { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first ServiceCategory ID from the query. +// Returns a *NotFoundError when no ServiceCategory ID was found. +func (_q *ServiceCategoryQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{servicecategory.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *ServiceCategoryQuery) FirstIDX(ctx context.Context) uuid.UUID { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single ServiceCategory entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one ServiceCategory entity is found. +// Returns a *NotFoundError when no ServiceCategory entities are found. +func (_q *ServiceCategoryQuery) Only(ctx context.Context) (*ServiceCategory, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{servicecategory.Label} + default: + return nil, &NotSingularError{servicecategory.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *ServiceCategoryQuery) OnlyX(ctx context.Context) *ServiceCategory { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only ServiceCategory ID in the query. +// Returns a *NotSingularError when more than one ServiceCategory ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *ServiceCategoryQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{servicecategory.Label} + default: + err = &NotSingularError{servicecategory.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *ServiceCategoryQuery) OnlyIDX(ctx context.Context) uuid.UUID { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of ServiceCategories. +func (_q *ServiceCategoryQuery) All(ctx context.Context) ([]*ServiceCategory, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*ServiceCategory, *ServiceCategoryQuery]() + return withInterceptors[[]*ServiceCategory](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *ServiceCategoryQuery) AllX(ctx context.Context) []*ServiceCategory { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of ServiceCategory IDs. +func (_q *ServiceCategoryQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(servicecategory.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *ServiceCategoryQuery) IDsX(ctx context.Context) []uuid.UUID { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *ServiceCategoryQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*ServiceCategoryQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *ServiceCategoryQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *ServiceCategoryQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *ServiceCategoryQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the ServiceCategoryQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *ServiceCategoryQuery) Clone() *ServiceCategoryQuery { + if _q == nil { + return nil + } + return &ServiceCategoryQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]servicecategory.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.ServiceCategory{}, _q.predicates...), + withChildren: _q.withChildren.Clone(), + withParent: _q.withParent.Clone(), + withLocalizedIn: _q.withLocalizedIn.Clone(), + withTranslations: _q.withTranslations.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// WithChildren tells the query-builder to eager-load the nodes that are connected to +// the "children" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ServiceCategoryQuery) WithChildren(opts ...func(*ServiceCategoryQuery)) *ServiceCategoryQuery { + query := (&ServiceCategoryClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withChildren = query + return _q +} + +// WithParent tells the query-builder to eager-load the nodes that are connected to +// the "parent" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ServiceCategoryQuery) WithParent(opts ...func(*ServiceCategoryQuery)) *ServiceCategoryQuery { + query := (&ServiceCategoryClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withParent = query + return _q +} + +// WithLocalizedIn tells the query-builder to eager-load the nodes that are connected to +// the "localized_in" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ServiceCategoryQuery) WithLocalizedIn(opts ...func(*SupportedLocaleQuery)) *ServiceCategoryQuery { + query := (&SupportedLocaleClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withLocalizedIn = query + return _q +} + +// WithTranslations tells the query-builder to eager-load the nodes that are connected to +// the "translations" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ServiceCategoryQuery) WithTranslations(opts ...func(*ServiceCategoryTranslationQuery)) *ServiceCategoryQuery { + query := (&ServiceCategoryTranslationClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withTranslations = query + return _q +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// ParentID uuid.UUID `json:"parent_id,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.ServiceCategory.Query(). +// GroupBy(servicecategory.FieldParentID). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *ServiceCategoryQuery) GroupBy(field string, fields ...string) *ServiceCategoryGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &ServiceCategoryGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = servicecategory.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// ParentID uuid.UUID `json:"parent_id,omitempty"` +// } +// +// client.ServiceCategory.Query(). +// Select(servicecategory.FieldParentID). +// Scan(ctx, &v) +func (_q *ServiceCategoryQuery) Select(fields ...string) *ServiceCategorySelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &ServiceCategorySelect{ServiceCategoryQuery: _q} + sbuild.label = servicecategory.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a ServiceCategorySelect configured with the given aggregations. +func (_q *ServiceCategoryQuery) Aggregate(fns ...AggregateFunc) *ServiceCategorySelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *ServiceCategoryQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !servicecategory.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *ServiceCategoryQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ServiceCategory, error) { + var ( + nodes = []*ServiceCategory{} + _spec = _q.querySpec() + loadedTypes = [4]bool{ + _q.withChildren != nil, + _q.withParent != nil, + _q.withLocalizedIn != nil, + _q.withTranslations != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*ServiceCategory).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &ServiceCategory{config: _q.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := _q.withChildren; query != nil { + if err := _q.loadChildren(ctx, query, nodes, + func(n *ServiceCategory) { n.Edges.Children = []*ServiceCategory{} }, + func(n *ServiceCategory, e *ServiceCategory) { n.Edges.Children = append(n.Edges.Children, e) }); err != nil { + return nil, err + } + } + if query := _q.withParent; query != nil { + if err := _q.loadParent(ctx, query, nodes, nil, + func(n *ServiceCategory, e *ServiceCategory) { n.Edges.Parent = e }); err != nil { + return nil, err + } + } + if query := _q.withLocalizedIn; query != nil { + if err := _q.loadLocalizedIn(ctx, query, nodes, + func(n *ServiceCategory) { n.Edges.LocalizedIn = []*SupportedLocale{} }, + func(n *ServiceCategory, e *SupportedLocale) { n.Edges.LocalizedIn = append(n.Edges.LocalizedIn, e) }); err != nil { + return nil, err + } + } + if query := _q.withTranslations; query != nil { + if err := _q.loadTranslations(ctx, query, nodes, + func(n *ServiceCategory) { n.Edges.Translations = []*ServiceCategoryTranslation{} }, + func(n *ServiceCategory, e *ServiceCategoryTranslation) { + n.Edges.Translations = append(n.Edges.Translations, e) + }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *ServiceCategoryQuery) loadChildren(ctx context.Context, query *ServiceCategoryQuery, nodes []*ServiceCategory, init func(*ServiceCategory), assign func(*ServiceCategory, *ServiceCategory)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[uuid.UUID]*ServiceCategory) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(servicecategory.FieldParentID) + } + query.Where(predicate.ServiceCategory(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(servicecategory.ChildrenColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.ParentID + if fk == nil { + return fmt.Errorf(`foreign-key "parent_id" is nil for node %v`, n.ID) + } + node, ok := nodeids[*fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "parent_id" returned %v for node %v`, *fk, n.ID) + } + assign(node, n) + } + return nil +} +func (_q *ServiceCategoryQuery) loadParent(ctx context.Context, query *ServiceCategoryQuery, nodes []*ServiceCategory, init func(*ServiceCategory), assign func(*ServiceCategory, *ServiceCategory)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*ServiceCategory) + for i := range nodes { + if nodes[i].ParentID == nil { + continue + } + fk := *nodes[i].ParentID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(servicecategory.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "parent_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (_q *ServiceCategoryQuery) loadLocalizedIn(ctx context.Context, query *SupportedLocaleQuery, nodes []*ServiceCategory, init func(*ServiceCategory), assign func(*ServiceCategory, *SupportedLocale)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[uuid.UUID]*ServiceCategory) + nids := make(map[string]map[*ServiceCategory]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(servicecategory.LocalizedInTable) + s.Join(joinT).On(s.C(supportedlocale.FieldID), joinT.C(servicecategory.LocalizedInPrimaryKey[1])) + s.Where(sql.InValues(joinT.C(servicecategory.LocalizedInPrimaryKey[0]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(servicecategory.LocalizedInPrimaryKey[0])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(uuid.UUID)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := *values[0].(*uuid.UUID) + inValue := values[1].(*sql.NullString).String + if nids[inValue] == nil { + nids[inValue] = map[*ServiceCategory]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*SupportedLocale](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "localized_in" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} +func (_q *ServiceCategoryQuery) loadTranslations(ctx context.Context, query *ServiceCategoryTranslationQuery, nodes []*ServiceCategory, init func(*ServiceCategory), assign func(*ServiceCategory, *ServiceCategoryTranslation)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[uuid.UUID]*ServiceCategory) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(servicecategorytranslation.FieldCategoryID) + } + query.Where(predicate.ServiceCategoryTranslation(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(servicecategory.TranslationsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.CategoryID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "category_id" returned %v for node %v`, fk, n) + } + assign(node, n) + } + return nil +} + +func (_q *ServiceCategoryQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *ServiceCategoryQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(servicecategory.Table, servicecategory.Columns, sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, servicecategory.FieldID) + for i := range fields { + if fields[i] != servicecategory.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if _q.withParent != nil { + _spec.Node.AddColumnOnce(servicecategory.FieldParentID) + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *ServiceCategoryQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(servicecategory.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = servicecategory.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ServiceCategoryGroupBy is the group-by builder for ServiceCategory entities. +type ServiceCategoryGroupBy struct { + selector + build *ServiceCategoryQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *ServiceCategoryGroupBy) Aggregate(fns ...AggregateFunc) *ServiceCategoryGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *ServiceCategoryGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ServiceCategoryQuery, *ServiceCategoryGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *ServiceCategoryGroupBy) sqlScan(ctx context.Context, root *ServiceCategoryQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// ServiceCategorySelect is the builder for selecting fields of ServiceCategory entities. +type ServiceCategorySelect struct { + *ServiceCategoryQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *ServiceCategorySelect) Aggregate(fns ...AggregateFunc) *ServiceCategorySelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *ServiceCategorySelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ServiceCategoryQuery, *ServiceCategorySelect](ctx, _s.ServiceCategoryQuery, _s, _s.inters, v) +} + +func (_s *ServiceCategorySelect) sqlScan(ctx context.Context, root *ServiceCategoryQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/servicecategory_update.go b/backend/ent/servicecategory_update.go new file mode 100644 index 0000000..a7453c5 --- /dev/null +++ b/backend/ent/servicecategory_update.go @@ -0,0 +1,772 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" + "github.com/google/uuid" +) + +// ServiceCategoryUpdate is the builder for updating ServiceCategory entities. +type ServiceCategoryUpdate struct { + config + hooks []Hook + mutation *ServiceCategoryMutation +} + +// Where appends a list predicates to the ServiceCategoryUpdate builder. +func (_u *ServiceCategoryUpdate) Where(ps ...predicate.ServiceCategory) *ServiceCategoryUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetParentID sets the "parent_id" field. +func (_u *ServiceCategoryUpdate) SetParentID(v uuid.UUID) *ServiceCategoryUpdate { + _u.mutation.SetParentID(v) + return _u +} + +// SetNillableParentID sets the "parent_id" field if the given value is not nil. +func (_u *ServiceCategoryUpdate) SetNillableParentID(v *uuid.UUID) *ServiceCategoryUpdate { + if v != nil { + _u.SetParentID(*v) + } + return _u +} + +// ClearParentID clears the value of the "parent_id" field. +func (_u *ServiceCategoryUpdate) ClearParentID() *ServiceCategoryUpdate { + _u.mutation.ClearParentID() + return _u +} + +// SetActive sets the "active" field. +func (_u *ServiceCategoryUpdate) SetActive(v bool) *ServiceCategoryUpdate { + _u.mutation.SetActive(v) + return _u +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_u *ServiceCategoryUpdate) SetNillableActive(v *bool) *ServiceCategoryUpdate { + if v != nil { + _u.SetActive(*v) + } + return _u +} + +// SetSortOrder sets the "sort_order" field. +func (_u *ServiceCategoryUpdate) SetSortOrder(v int) *ServiceCategoryUpdate { + _u.mutation.ResetSortOrder() + _u.mutation.SetSortOrder(v) + return _u +} + +// SetNillableSortOrder sets the "sort_order" field if the given value is not nil. +func (_u *ServiceCategoryUpdate) SetNillableSortOrder(v *int) *ServiceCategoryUpdate { + if v != nil { + _u.SetSortOrder(*v) + } + return _u +} + +// AddSortOrder adds value to the "sort_order" field. +func (_u *ServiceCategoryUpdate) AddSortOrder(v int) *ServiceCategoryUpdate { + _u.mutation.AddSortOrder(v) + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *ServiceCategoryUpdate) SetUpdatedAt(v time.Time) *ServiceCategoryUpdate { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// AddChildIDs adds the "children" edge to the ServiceCategory entity by IDs. +func (_u *ServiceCategoryUpdate) AddChildIDs(ids ...uuid.UUID) *ServiceCategoryUpdate { + _u.mutation.AddChildIDs(ids...) + return _u +} + +// AddChildren adds the "children" edges to the ServiceCategory entity. +func (_u *ServiceCategoryUpdate) AddChildren(v ...*ServiceCategory) *ServiceCategoryUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddChildIDs(ids...) +} + +// SetParent sets the "parent" edge to the ServiceCategory entity. +func (_u *ServiceCategoryUpdate) SetParent(v *ServiceCategory) *ServiceCategoryUpdate { + return _u.SetParentID(v.ID) +} + +// AddLocalizedInIDs adds the "localized_in" edge to the SupportedLocale entity by IDs. +func (_u *ServiceCategoryUpdate) AddLocalizedInIDs(ids ...string) *ServiceCategoryUpdate { + _u.mutation.AddLocalizedInIDs(ids...) + return _u +} + +// AddLocalizedIn adds the "localized_in" edges to the SupportedLocale entity. +func (_u *ServiceCategoryUpdate) AddLocalizedIn(v ...*SupportedLocale) *ServiceCategoryUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddLocalizedInIDs(ids...) +} + +// Mutation returns the ServiceCategoryMutation object of the builder. +func (_u *ServiceCategoryUpdate) Mutation() *ServiceCategoryMutation { + return _u.mutation +} + +// ClearChildren clears all "children" edges to the ServiceCategory entity. +func (_u *ServiceCategoryUpdate) ClearChildren() *ServiceCategoryUpdate { + _u.mutation.ClearChildren() + return _u +} + +// RemoveChildIDs removes the "children" edge to ServiceCategory entities by IDs. +func (_u *ServiceCategoryUpdate) RemoveChildIDs(ids ...uuid.UUID) *ServiceCategoryUpdate { + _u.mutation.RemoveChildIDs(ids...) + return _u +} + +// RemoveChildren removes "children" edges to ServiceCategory entities. +func (_u *ServiceCategoryUpdate) RemoveChildren(v ...*ServiceCategory) *ServiceCategoryUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveChildIDs(ids...) +} + +// ClearParent clears the "parent" edge to the ServiceCategory entity. +func (_u *ServiceCategoryUpdate) ClearParent() *ServiceCategoryUpdate { + _u.mutation.ClearParent() + return _u +} + +// ClearLocalizedIn clears all "localized_in" edges to the SupportedLocale entity. +func (_u *ServiceCategoryUpdate) ClearLocalizedIn() *ServiceCategoryUpdate { + _u.mutation.ClearLocalizedIn() + return _u +} + +// RemoveLocalizedInIDs removes the "localized_in" edge to SupportedLocale entities by IDs. +func (_u *ServiceCategoryUpdate) RemoveLocalizedInIDs(ids ...string) *ServiceCategoryUpdate { + _u.mutation.RemoveLocalizedInIDs(ids...) + return _u +} + +// RemoveLocalizedIn removes "localized_in" edges to SupportedLocale entities. +func (_u *ServiceCategoryUpdate) RemoveLocalizedIn(v ...*SupportedLocale) *ServiceCategoryUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveLocalizedInIDs(ids...) +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *ServiceCategoryUpdate) Save(ctx context.Context) (int, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *ServiceCategoryUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *ServiceCategoryUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *ServiceCategoryUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *ServiceCategoryUpdate) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := servicecategory.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *ServiceCategoryUpdate) check() error { + if v, ok := _u.mutation.SortOrder(); ok { + if err := servicecategory.SortOrderValidator(v); err != nil { + return &ValidationError{Name: "sort_order", err: fmt.Errorf(`ent: validator failed for field "ServiceCategory.sort_order": %w`, err)} + } + } + return nil +} + +func (_u *ServiceCategoryUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(servicecategory.Table, servicecategory.Columns, sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Active(); ok { + _spec.SetField(servicecategory.FieldActive, field.TypeBool, value) + } + if value, ok := _u.mutation.SortOrder(); ok { + _spec.SetField(servicecategory.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedSortOrder(); ok { + _spec.AddField(servicecategory.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(servicecategory.FieldUpdatedAt, field.TypeTime, value) + } + if _u.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: servicecategory.ChildrenTable, + Columns: []string{servicecategory.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedChildrenIDs(); len(nodes) > 0 && !_u.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: servicecategory.ChildrenTable, + Columns: []string{servicecategory.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.ChildrenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: servicecategory.ChildrenTable, + Columns: []string{servicecategory.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _u.mutation.ParentCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: servicecategory.ParentTable, + Columns: []string{servicecategory.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.ParentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: servicecategory.ParentTable, + Columns: []string{servicecategory.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _u.mutation.LocalizedInCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: servicecategory.LocalizedInTable, + Columns: servicecategory.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedLocalizedInIDs(); len(nodes) > 0 && !_u.mutation.LocalizedInCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: servicecategory.LocalizedInTable, + Columns: servicecategory.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.LocalizedInIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: servicecategory.LocalizedInTable, + Columns: servicecategory.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{servicecategory.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// ServiceCategoryUpdateOne is the builder for updating a single ServiceCategory entity. +type ServiceCategoryUpdateOne struct { + config + fields []string + hooks []Hook + mutation *ServiceCategoryMutation +} + +// SetParentID sets the "parent_id" field. +func (_u *ServiceCategoryUpdateOne) SetParentID(v uuid.UUID) *ServiceCategoryUpdateOne { + _u.mutation.SetParentID(v) + return _u +} + +// SetNillableParentID sets the "parent_id" field if the given value is not nil. +func (_u *ServiceCategoryUpdateOne) SetNillableParentID(v *uuid.UUID) *ServiceCategoryUpdateOne { + if v != nil { + _u.SetParentID(*v) + } + return _u +} + +// ClearParentID clears the value of the "parent_id" field. +func (_u *ServiceCategoryUpdateOne) ClearParentID() *ServiceCategoryUpdateOne { + _u.mutation.ClearParentID() + return _u +} + +// SetActive sets the "active" field. +func (_u *ServiceCategoryUpdateOne) SetActive(v bool) *ServiceCategoryUpdateOne { + _u.mutation.SetActive(v) + return _u +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_u *ServiceCategoryUpdateOne) SetNillableActive(v *bool) *ServiceCategoryUpdateOne { + if v != nil { + _u.SetActive(*v) + } + return _u +} + +// SetSortOrder sets the "sort_order" field. +func (_u *ServiceCategoryUpdateOne) SetSortOrder(v int) *ServiceCategoryUpdateOne { + _u.mutation.ResetSortOrder() + _u.mutation.SetSortOrder(v) + return _u +} + +// SetNillableSortOrder sets the "sort_order" field if the given value is not nil. +func (_u *ServiceCategoryUpdateOne) SetNillableSortOrder(v *int) *ServiceCategoryUpdateOne { + if v != nil { + _u.SetSortOrder(*v) + } + return _u +} + +// AddSortOrder adds value to the "sort_order" field. +func (_u *ServiceCategoryUpdateOne) AddSortOrder(v int) *ServiceCategoryUpdateOne { + _u.mutation.AddSortOrder(v) + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *ServiceCategoryUpdateOne) SetUpdatedAt(v time.Time) *ServiceCategoryUpdateOne { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// AddChildIDs adds the "children" edge to the ServiceCategory entity by IDs. +func (_u *ServiceCategoryUpdateOne) AddChildIDs(ids ...uuid.UUID) *ServiceCategoryUpdateOne { + _u.mutation.AddChildIDs(ids...) + return _u +} + +// AddChildren adds the "children" edges to the ServiceCategory entity. +func (_u *ServiceCategoryUpdateOne) AddChildren(v ...*ServiceCategory) *ServiceCategoryUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddChildIDs(ids...) +} + +// SetParent sets the "parent" edge to the ServiceCategory entity. +func (_u *ServiceCategoryUpdateOne) SetParent(v *ServiceCategory) *ServiceCategoryUpdateOne { + return _u.SetParentID(v.ID) +} + +// AddLocalizedInIDs adds the "localized_in" edge to the SupportedLocale entity by IDs. +func (_u *ServiceCategoryUpdateOne) AddLocalizedInIDs(ids ...string) *ServiceCategoryUpdateOne { + _u.mutation.AddLocalizedInIDs(ids...) + return _u +} + +// AddLocalizedIn adds the "localized_in" edges to the SupportedLocale entity. +func (_u *ServiceCategoryUpdateOne) AddLocalizedIn(v ...*SupportedLocale) *ServiceCategoryUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddLocalizedInIDs(ids...) +} + +// Mutation returns the ServiceCategoryMutation object of the builder. +func (_u *ServiceCategoryUpdateOne) Mutation() *ServiceCategoryMutation { + return _u.mutation +} + +// ClearChildren clears all "children" edges to the ServiceCategory entity. +func (_u *ServiceCategoryUpdateOne) ClearChildren() *ServiceCategoryUpdateOne { + _u.mutation.ClearChildren() + return _u +} + +// RemoveChildIDs removes the "children" edge to ServiceCategory entities by IDs. +func (_u *ServiceCategoryUpdateOne) RemoveChildIDs(ids ...uuid.UUID) *ServiceCategoryUpdateOne { + _u.mutation.RemoveChildIDs(ids...) + return _u +} + +// RemoveChildren removes "children" edges to ServiceCategory entities. +func (_u *ServiceCategoryUpdateOne) RemoveChildren(v ...*ServiceCategory) *ServiceCategoryUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveChildIDs(ids...) +} + +// ClearParent clears the "parent" edge to the ServiceCategory entity. +func (_u *ServiceCategoryUpdateOne) ClearParent() *ServiceCategoryUpdateOne { + _u.mutation.ClearParent() + return _u +} + +// ClearLocalizedIn clears all "localized_in" edges to the SupportedLocale entity. +func (_u *ServiceCategoryUpdateOne) ClearLocalizedIn() *ServiceCategoryUpdateOne { + _u.mutation.ClearLocalizedIn() + return _u +} + +// RemoveLocalizedInIDs removes the "localized_in" edge to SupportedLocale entities by IDs. +func (_u *ServiceCategoryUpdateOne) RemoveLocalizedInIDs(ids ...string) *ServiceCategoryUpdateOne { + _u.mutation.RemoveLocalizedInIDs(ids...) + return _u +} + +// RemoveLocalizedIn removes "localized_in" edges to SupportedLocale entities. +func (_u *ServiceCategoryUpdateOne) RemoveLocalizedIn(v ...*SupportedLocale) *ServiceCategoryUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveLocalizedInIDs(ids...) +} + +// Where appends a list predicates to the ServiceCategoryUpdate builder. +func (_u *ServiceCategoryUpdateOne) Where(ps ...predicate.ServiceCategory) *ServiceCategoryUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *ServiceCategoryUpdateOne) Select(field string, fields ...string) *ServiceCategoryUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated ServiceCategory entity. +func (_u *ServiceCategoryUpdateOne) Save(ctx context.Context) (*ServiceCategory, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *ServiceCategoryUpdateOne) SaveX(ctx context.Context) *ServiceCategory { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *ServiceCategoryUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *ServiceCategoryUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *ServiceCategoryUpdateOne) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := servicecategory.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *ServiceCategoryUpdateOne) check() error { + if v, ok := _u.mutation.SortOrder(); ok { + if err := servicecategory.SortOrderValidator(v); err != nil { + return &ValidationError{Name: "sort_order", err: fmt.Errorf(`ent: validator failed for field "ServiceCategory.sort_order": %w`, err)} + } + } + return nil +} + +func (_u *ServiceCategoryUpdateOne) sqlSave(ctx context.Context) (_node *ServiceCategory, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(servicecategory.Table, servicecategory.Columns, sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "ServiceCategory.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, servicecategory.FieldID) + for _, f := range fields { + if !servicecategory.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != servicecategory.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Active(); ok { + _spec.SetField(servicecategory.FieldActive, field.TypeBool, value) + } + if value, ok := _u.mutation.SortOrder(); ok { + _spec.SetField(servicecategory.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedSortOrder(); ok { + _spec.AddField(servicecategory.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(servicecategory.FieldUpdatedAt, field.TypeTime, value) + } + if _u.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: servicecategory.ChildrenTable, + Columns: []string{servicecategory.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedChildrenIDs(); len(nodes) > 0 && !_u.mutation.ChildrenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: servicecategory.ChildrenTable, + Columns: []string{servicecategory.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.ChildrenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: servicecategory.ChildrenTable, + Columns: []string{servicecategory.ChildrenColumn}, + Bidi: true, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _u.mutation.ParentCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: servicecategory.ParentTable, + Columns: []string{servicecategory.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.ParentIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: servicecategory.ParentTable, + Columns: []string{servicecategory.ParentColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _u.mutation.LocalizedInCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: servicecategory.LocalizedInTable, + Columns: servicecategory.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedLocalizedInIDs(); len(nodes) > 0 && !_u.mutation.LocalizedInCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: servicecategory.LocalizedInTable, + Columns: servicecategory.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.LocalizedInIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: servicecategory.LocalizedInTable, + Columns: servicecategory.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &ServiceCategory{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{servicecategory.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/ent/servicecategorytranslation.go b/backend/ent/servicecategorytranslation.go new file mode 100644 index 0000000..ed01a84 --- /dev/null +++ b/backend/ent/servicecategorytranslation.go @@ -0,0 +1,179 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategorytranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" + "github.com/google/uuid" +) + +// ServiceCategoryTranslation is the model entity for the ServiceCategoryTranslation schema. +type ServiceCategoryTranslation struct { + config `json:"-"` + // CategoryID holds the value of the "category_id" field. + CategoryID uuid.UUID `json:"category_id,omitempty"` + // Locale holds the value of the "locale" field. + Locale string `json:"locale,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` + // Description holds the value of the "description" field. + Description *string `json:"description,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the ServiceCategoryTranslationQuery when eager-loading is set. + Edges ServiceCategoryTranslationEdges `json:"edges"` + selectValues sql.SelectValues +} + +// ServiceCategoryTranslationEdges holds the relations/edges for other nodes in the graph. +type ServiceCategoryTranslationEdges struct { + // Category holds the value of the category edge. + Category *ServiceCategory `json:"category,omitempty"` + // LocaleRecord holds the value of the locale_record edge. + LocaleRecord *SupportedLocale `json:"locale_record,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [2]bool +} + +// CategoryOrErr returns the Category value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e ServiceCategoryTranslationEdges) CategoryOrErr() (*ServiceCategory, error) { + if e.Category != nil { + return e.Category, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: servicecategory.Label} + } + return nil, &NotLoadedError{edge: "category"} +} + +// LocaleRecordOrErr returns the LocaleRecord value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e ServiceCategoryTranslationEdges) LocaleRecordOrErr() (*SupportedLocale, error) { + if e.LocaleRecord != nil { + return e.LocaleRecord, nil + } else if e.loadedTypes[1] { + return nil, &NotFoundError{label: supportedlocale.Label} + } + return nil, &NotLoadedError{edge: "locale_record"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*ServiceCategoryTranslation) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case servicecategorytranslation.FieldLocale, servicecategorytranslation.FieldName, servicecategorytranslation.FieldDescription: + values[i] = new(sql.NullString) + case servicecategorytranslation.FieldCategoryID: + values[i] = new(uuid.UUID) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the ServiceCategoryTranslation fields. +func (_m *ServiceCategoryTranslation) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case servicecategorytranslation.FieldCategoryID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field category_id", values[i]) + } else if value != nil { + _m.CategoryID = *value + } + case servicecategorytranslation.FieldLocale: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field locale", values[i]) + } else if value.Valid { + _m.Locale = value.String + } + case servicecategorytranslation.FieldName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field name", values[i]) + } else if value.Valid { + _m.Name = value.String + } + case servicecategorytranslation.FieldDescription: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field description", values[i]) + } else if value.Valid { + _m.Description = new(string) + *_m.Description = value.String + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the ServiceCategoryTranslation. +// This includes values selected through modifiers, order, etc. +func (_m *ServiceCategoryTranslation) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QueryCategory queries the "category" edge of the ServiceCategoryTranslation entity. +func (_m *ServiceCategoryTranslation) QueryCategory() *ServiceCategoryQuery { + return NewServiceCategoryTranslationClient(_m.config).QueryCategory(_m) +} + +// QueryLocaleRecord queries the "locale_record" edge of the ServiceCategoryTranslation entity. +func (_m *ServiceCategoryTranslation) QueryLocaleRecord() *SupportedLocaleQuery { + return NewServiceCategoryTranslationClient(_m.config).QueryLocaleRecord(_m) +} + +// Update returns a builder for updating this ServiceCategoryTranslation. +// Note that you need to call ServiceCategoryTranslation.Unwrap() before calling this method if this ServiceCategoryTranslation +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *ServiceCategoryTranslation) Update() *ServiceCategoryTranslationUpdateOne { + return NewServiceCategoryTranslationClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the ServiceCategoryTranslation entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *ServiceCategoryTranslation) Unwrap() *ServiceCategoryTranslation { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: ServiceCategoryTranslation is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *ServiceCategoryTranslation) String() string { + var builder strings.Builder + builder.WriteString("ServiceCategoryTranslation(") + builder.WriteString("category_id=") + builder.WriteString(fmt.Sprintf("%v", _m.CategoryID)) + builder.WriteString(", ") + builder.WriteString("locale=") + builder.WriteString(_m.Locale) + builder.WriteString(", ") + builder.WriteString("name=") + builder.WriteString(_m.Name) + builder.WriteString(", ") + if v := _m.Description; v != nil { + builder.WriteString("description=") + builder.WriteString(*v) + } + builder.WriteByte(')') + return builder.String() +} + +// ServiceCategoryTranslations is a parsable slice of ServiceCategoryTranslation. +type ServiceCategoryTranslations []*ServiceCategoryTranslation diff --git a/backend/ent/servicecategorytranslation/servicecategorytranslation.go b/backend/ent/servicecategorytranslation/servicecategorytranslation.go new file mode 100644 index 0000000..92c33e7 --- /dev/null +++ b/backend/ent/servicecategorytranslation/servicecategorytranslation.go @@ -0,0 +1,123 @@ +// Code generated by ent, DO NOT EDIT. + +package servicecategorytranslation + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the servicecategorytranslation type in the database. + Label = "service_category_translation" + // FieldCategoryID holds the string denoting the category_id field in the database. + FieldCategoryID = "category_id" + // FieldLocale holds the string denoting the locale field in the database. + FieldLocale = "locale" + // FieldName holds the string denoting the name field in the database. + FieldName = "name" + // FieldDescription holds the string denoting the description field in the database. + FieldDescription = "description" + // EdgeCategory holds the string denoting the category edge name in mutations. + EdgeCategory = "category" + // EdgeLocaleRecord holds the string denoting the locale_record edge name in mutations. + EdgeLocaleRecord = "locale_record" + // ServiceCategoryFieldID holds the string denoting the ID field of the ServiceCategory. + ServiceCategoryFieldID = "id" + // SupportedLocaleFieldID holds the string denoting the ID field of the SupportedLocale. + SupportedLocaleFieldID = "id" + // Table holds the table name of the servicecategorytranslation in the database. + Table = "service_category_translations" + // CategoryTable is the table that holds the category relation/edge. + CategoryTable = "service_category_translations" + // CategoryInverseTable is the table name for the ServiceCategory entity. + // It exists in this package in order to avoid circular dependency with the "servicecategory" package. + CategoryInverseTable = "service_categories" + // CategoryColumn is the table column denoting the category relation/edge. + CategoryColumn = "category_id" + // LocaleRecordTable is the table that holds the locale_record relation/edge. + LocaleRecordTable = "service_category_translations" + // LocaleRecordInverseTable is the table name for the SupportedLocale entity. + // It exists in this package in order to avoid circular dependency with the "supportedlocale" package. + LocaleRecordInverseTable = "supported_locales" + // LocaleRecordColumn is the table column denoting the locale_record relation/edge. + LocaleRecordColumn = "locale" +) + +// Columns holds all SQL columns for servicecategorytranslation fields. +var Columns = []string{ + FieldCategoryID, + FieldLocale, + FieldName, + FieldDescription, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // LocaleValidator is a validator for the "locale" field. It is called by the builders before save. + LocaleValidator func(string) error + // NameValidator is a validator for the "name" field. It is called by the builders before save. + NameValidator func(string) error + // DescriptionValidator is a validator for the "description" field. It is called by the builders before save. + DescriptionValidator func(string) error +) + +// OrderOption defines the ordering options for the ServiceCategoryTranslation queries. +type OrderOption func(*sql.Selector) + +// ByCategoryID orders the results by the category_id field. +func ByCategoryID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCategoryID, opts...).ToFunc() +} + +// ByLocale orders the results by the locale field. +func ByLocale(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLocale, opts...).ToFunc() +} + +// ByName orders the results by the name field. +func ByName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldName, opts...).ToFunc() +} + +// ByDescription orders the results by the description field. +func ByDescription(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldDescription, opts...).ToFunc() +} + +// ByCategoryField orders the results by category field. +func ByCategoryField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newCategoryStep(), sql.OrderByField(field, opts...)) + } +} + +// ByLocaleRecordField orders the results by locale_record field. +func ByLocaleRecordField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newLocaleRecordStep(), sql.OrderByField(field, opts...)) + } +} +func newCategoryStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, CategoryColumn), + sqlgraph.To(CategoryInverseTable, ServiceCategoryFieldID), + sqlgraph.Edge(sqlgraph.M2O, false, CategoryTable, CategoryColumn), + ) +} +func newLocaleRecordStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, LocaleRecordColumn), + sqlgraph.To(LocaleRecordInverseTable, SupportedLocaleFieldID), + sqlgraph.Edge(sqlgraph.M2O, false, LocaleRecordTable, LocaleRecordColumn), + ) +} diff --git a/backend/ent/servicecategorytranslation/where.go b/backend/ent/servicecategorytranslation/where.go new file mode 100644 index 0000000..4a2a711 --- /dev/null +++ b/backend/ent/servicecategorytranslation/where.go @@ -0,0 +1,316 @@ +// Code generated by ent, DO NOT EDIT. + +package servicecategorytranslation + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/google/uuid" +) + +// CategoryID applies equality check predicate on the "category_id" field. It's identical to CategoryIDEQ. +func CategoryID(v uuid.UUID) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldEQ(FieldCategoryID, v)) +} + +// Locale applies equality check predicate on the "locale" field. It's identical to LocaleEQ. +func Locale(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldEQ(FieldLocale, v)) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldEQ(FieldName, v)) +} + +// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ. +func Description(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldEQ(FieldDescription, v)) +} + +// CategoryIDEQ applies the EQ predicate on the "category_id" field. +func CategoryIDEQ(v uuid.UUID) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldEQ(FieldCategoryID, v)) +} + +// CategoryIDNEQ applies the NEQ predicate on the "category_id" field. +func CategoryIDNEQ(v uuid.UUID) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldNEQ(FieldCategoryID, v)) +} + +// CategoryIDIn applies the In predicate on the "category_id" field. +func CategoryIDIn(vs ...uuid.UUID) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldIn(FieldCategoryID, vs...)) +} + +// CategoryIDNotIn applies the NotIn predicate on the "category_id" field. +func CategoryIDNotIn(vs ...uuid.UUID) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldNotIn(FieldCategoryID, vs...)) +} + +// LocaleEQ applies the EQ predicate on the "locale" field. +func LocaleEQ(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldEQ(FieldLocale, v)) +} + +// LocaleNEQ applies the NEQ predicate on the "locale" field. +func LocaleNEQ(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldNEQ(FieldLocale, v)) +} + +// LocaleIn applies the In predicate on the "locale" field. +func LocaleIn(vs ...string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldIn(FieldLocale, vs...)) +} + +// LocaleNotIn applies the NotIn predicate on the "locale" field. +func LocaleNotIn(vs ...string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldNotIn(FieldLocale, vs...)) +} + +// LocaleGT applies the GT predicate on the "locale" field. +func LocaleGT(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldGT(FieldLocale, v)) +} + +// LocaleGTE applies the GTE predicate on the "locale" field. +func LocaleGTE(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldGTE(FieldLocale, v)) +} + +// LocaleLT applies the LT predicate on the "locale" field. +func LocaleLT(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldLT(FieldLocale, v)) +} + +// LocaleLTE applies the LTE predicate on the "locale" field. +func LocaleLTE(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldLTE(FieldLocale, v)) +} + +// LocaleContains applies the Contains predicate on the "locale" field. +func LocaleContains(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldContains(FieldLocale, v)) +} + +// LocaleHasPrefix applies the HasPrefix predicate on the "locale" field. +func LocaleHasPrefix(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldHasPrefix(FieldLocale, v)) +} + +// LocaleHasSuffix applies the HasSuffix predicate on the "locale" field. +func LocaleHasSuffix(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldHasSuffix(FieldLocale, v)) +} + +// LocaleEqualFold applies the EqualFold predicate on the "locale" field. +func LocaleEqualFold(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldEqualFold(FieldLocale, v)) +} + +// LocaleContainsFold applies the ContainsFold predicate on the "locale" field. +func LocaleContainsFold(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldContainsFold(FieldLocale, v)) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldEQ(FieldName, v)) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldNEQ(FieldName, v)) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldIn(FieldName, vs...)) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldNotIn(FieldName, vs...)) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldGT(FieldName, v)) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldGTE(FieldName, v)) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldLT(FieldName, v)) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldLTE(FieldName, v)) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldContains(FieldName, v)) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldHasPrefix(FieldName, v)) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldHasSuffix(FieldName, v)) +} + +// NameEqualFold applies the EqualFold predicate on the "name" field. +func NameEqualFold(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldEqualFold(FieldName, v)) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldContainsFold(FieldName, v)) +} + +// DescriptionEQ applies the EQ predicate on the "description" field. +func DescriptionEQ(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldEQ(FieldDescription, v)) +} + +// DescriptionNEQ applies the NEQ predicate on the "description" field. +func DescriptionNEQ(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldNEQ(FieldDescription, v)) +} + +// DescriptionIn applies the In predicate on the "description" field. +func DescriptionIn(vs ...string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldIn(FieldDescription, vs...)) +} + +// DescriptionNotIn applies the NotIn predicate on the "description" field. +func DescriptionNotIn(vs ...string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldNotIn(FieldDescription, vs...)) +} + +// DescriptionGT applies the GT predicate on the "description" field. +func DescriptionGT(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldGT(FieldDescription, v)) +} + +// DescriptionGTE applies the GTE predicate on the "description" field. +func DescriptionGTE(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldGTE(FieldDescription, v)) +} + +// DescriptionLT applies the LT predicate on the "description" field. +func DescriptionLT(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldLT(FieldDescription, v)) +} + +// DescriptionLTE applies the LTE predicate on the "description" field. +func DescriptionLTE(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldLTE(FieldDescription, v)) +} + +// DescriptionContains applies the Contains predicate on the "description" field. +func DescriptionContains(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldContains(FieldDescription, v)) +} + +// DescriptionHasPrefix applies the HasPrefix predicate on the "description" field. +func DescriptionHasPrefix(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldHasPrefix(FieldDescription, v)) +} + +// DescriptionHasSuffix applies the HasSuffix predicate on the "description" field. +func DescriptionHasSuffix(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldHasSuffix(FieldDescription, v)) +} + +// DescriptionIsNil applies the IsNil predicate on the "description" field. +func DescriptionIsNil() predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldIsNull(FieldDescription)) +} + +// DescriptionNotNil applies the NotNil predicate on the "description" field. +func DescriptionNotNil() predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldNotNull(FieldDescription)) +} + +// DescriptionEqualFold applies the EqualFold predicate on the "description" field. +func DescriptionEqualFold(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldEqualFold(FieldDescription, v)) +} + +// DescriptionContainsFold applies the ContainsFold predicate on the "description" field. +func DescriptionContainsFold(v string) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.FieldContainsFold(FieldDescription, v)) +} + +// HasCategory applies the HasEdge predicate on the "category" edge. +func HasCategory() predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, CategoryColumn), + sqlgraph.Edge(sqlgraph.M2O, false, CategoryTable, CategoryColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasCategoryWith applies the HasEdge predicate on the "category" edge with a given conditions (other predicates). +func HasCategoryWith(preds ...predicate.ServiceCategory) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(func(s *sql.Selector) { + step := newCategoryStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasLocaleRecord applies the HasEdge predicate on the "locale_record" edge. +func HasLocaleRecord() predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, LocaleRecordColumn), + sqlgraph.Edge(sqlgraph.M2O, false, LocaleRecordTable, LocaleRecordColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasLocaleRecordWith applies the HasEdge predicate on the "locale_record" edge with a given conditions (other predicates). +func HasLocaleRecordWith(preds ...predicate.SupportedLocale) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(func(s *sql.Selector) { + step := newLocaleRecordStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.ServiceCategoryTranslation) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.ServiceCategoryTranslation) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.ServiceCategoryTranslation) predicate.ServiceCategoryTranslation { + return predicate.ServiceCategoryTranslation(sql.NotPredicates(p)) +} diff --git a/backend/ent/servicecategorytranslation_create.go b/backend/ent/servicecategorytranslation_create.go new file mode 100644 index 0000000..010cac4 --- /dev/null +++ b/backend/ent/servicecategorytranslation_create.go @@ -0,0 +1,284 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategorytranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" + "github.com/google/uuid" +) + +// ServiceCategoryTranslationCreate is the builder for creating a ServiceCategoryTranslation entity. +type ServiceCategoryTranslationCreate struct { + config + mutation *ServiceCategoryTranslationMutation + hooks []Hook +} + +// SetCategoryID sets the "category_id" field. +func (_c *ServiceCategoryTranslationCreate) SetCategoryID(v uuid.UUID) *ServiceCategoryTranslationCreate { + _c.mutation.SetCategoryID(v) + return _c +} + +// SetLocale sets the "locale" field. +func (_c *ServiceCategoryTranslationCreate) SetLocale(v string) *ServiceCategoryTranslationCreate { + _c.mutation.SetLocale(v) + return _c +} + +// SetName sets the "name" field. +func (_c *ServiceCategoryTranslationCreate) SetName(v string) *ServiceCategoryTranslationCreate { + _c.mutation.SetName(v) + return _c +} + +// SetDescription sets the "description" field. +func (_c *ServiceCategoryTranslationCreate) SetDescription(v string) *ServiceCategoryTranslationCreate { + _c.mutation.SetDescription(v) + return _c +} + +// SetNillableDescription sets the "description" field if the given value is not nil. +func (_c *ServiceCategoryTranslationCreate) SetNillableDescription(v *string) *ServiceCategoryTranslationCreate { + if v != nil { + _c.SetDescription(*v) + } + return _c +} + +// SetCategory sets the "category" edge to the ServiceCategory entity. +func (_c *ServiceCategoryTranslationCreate) SetCategory(v *ServiceCategory) *ServiceCategoryTranslationCreate { + return _c.SetCategoryID(v.ID) +} + +// SetLocaleRecordID sets the "locale_record" edge to the SupportedLocale entity by ID. +func (_c *ServiceCategoryTranslationCreate) SetLocaleRecordID(id string) *ServiceCategoryTranslationCreate { + _c.mutation.SetLocaleRecordID(id) + return _c +} + +// SetLocaleRecord sets the "locale_record" edge to the SupportedLocale entity. +func (_c *ServiceCategoryTranslationCreate) SetLocaleRecord(v *SupportedLocale) *ServiceCategoryTranslationCreate { + return _c.SetLocaleRecordID(v.ID) +} + +// Mutation returns the ServiceCategoryTranslationMutation object of the builder. +func (_c *ServiceCategoryTranslationCreate) Mutation() *ServiceCategoryTranslationMutation { + return _c.mutation +} + +// Save creates the ServiceCategoryTranslation in the database. +func (_c *ServiceCategoryTranslationCreate) Save(ctx context.Context) (*ServiceCategoryTranslation, error) { + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *ServiceCategoryTranslationCreate) SaveX(ctx context.Context) *ServiceCategoryTranslation { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *ServiceCategoryTranslationCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *ServiceCategoryTranslationCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *ServiceCategoryTranslationCreate) check() error { + if _, ok := _c.mutation.CategoryID(); !ok { + return &ValidationError{Name: "category_id", err: errors.New(`ent: missing required field "ServiceCategoryTranslation.category_id"`)} + } + if _, ok := _c.mutation.Locale(); !ok { + return &ValidationError{Name: "locale", err: errors.New(`ent: missing required field "ServiceCategoryTranslation.locale"`)} + } + if v, ok := _c.mutation.Locale(); ok { + if err := servicecategorytranslation.LocaleValidator(v); err != nil { + return &ValidationError{Name: "locale", err: fmt.Errorf(`ent: validator failed for field "ServiceCategoryTranslation.locale": %w`, err)} + } + } + if _, ok := _c.mutation.Name(); !ok { + return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "ServiceCategoryTranslation.name"`)} + } + if v, ok := _c.mutation.Name(); ok { + if err := servicecategorytranslation.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "ServiceCategoryTranslation.name": %w`, err)} + } + } + if v, ok := _c.mutation.Description(); ok { + if err := servicecategorytranslation.DescriptionValidator(v); err != nil { + return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "ServiceCategoryTranslation.description": %w`, err)} + } + } + if len(_c.mutation.CategoryIDs()) == 0 { + return &ValidationError{Name: "category", err: errors.New(`ent: missing required edge "ServiceCategoryTranslation.category"`)} + } + if len(_c.mutation.LocaleRecordIDs()) == 0 { + return &ValidationError{Name: "locale_record", err: errors.New(`ent: missing required edge "ServiceCategoryTranslation.locale_record"`)} + } + return nil +} + +func (_c *ServiceCategoryTranslationCreate) sqlSave(ctx context.Context) (*ServiceCategoryTranslation, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + return _node, nil +} + +func (_c *ServiceCategoryTranslationCreate) createSpec() (*ServiceCategoryTranslation, *sqlgraph.CreateSpec) { + var ( + _node = &ServiceCategoryTranslation{config: _c.config} + _spec = sqlgraph.NewCreateSpec(servicecategorytranslation.Table, nil) + ) + if value, ok := _c.mutation.Name(); ok { + _spec.SetField(servicecategorytranslation.FieldName, field.TypeString, value) + _node.Name = value + } + if value, ok := _c.mutation.Description(); ok { + _spec.SetField(servicecategorytranslation.FieldDescription, field.TypeString, value) + _node.Description = &value + } + if nodes := _c.mutation.CategoryIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: servicecategorytranslation.CategoryTable, + Columns: []string{servicecategorytranslation.CategoryColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.CategoryID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.LocaleRecordIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: servicecategorytranslation.LocaleRecordTable, + Columns: []string{servicecategorytranslation.LocaleRecordColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.Locale = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// ServiceCategoryTranslationCreateBulk is the builder for creating many ServiceCategoryTranslation entities in bulk. +type ServiceCategoryTranslationCreateBulk struct { + config + err error + builders []*ServiceCategoryTranslationCreate +} + +// Save creates the ServiceCategoryTranslation entities in the database. +func (_c *ServiceCategoryTranslationCreateBulk) Save(ctx context.Context) ([]*ServiceCategoryTranslation, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*ServiceCategoryTranslation, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*ServiceCategoryTranslationMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *ServiceCategoryTranslationCreateBulk) SaveX(ctx context.Context) []*ServiceCategoryTranslation { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *ServiceCategoryTranslationCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *ServiceCategoryTranslationCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/servicecategorytranslation_delete.go b/backend/ent/servicecategorytranslation_delete.go new file mode 100644 index 0000000..6993d50 --- /dev/null +++ b/backend/ent/servicecategorytranslation_delete.go @@ -0,0 +1,87 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategorytranslation" +) + +// ServiceCategoryTranslationDelete is the builder for deleting a ServiceCategoryTranslation entity. +type ServiceCategoryTranslationDelete struct { + config + hooks []Hook + mutation *ServiceCategoryTranslationMutation +} + +// Where appends a list predicates to the ServiceCategoryTranslationDelete builder. +func (_d *ServiceCategoryTranslationDelete) Where(ps ...predicate.ServiceCategoryTranslation) *ServiceCategoryTranslationDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *ServiceCategoryTranslationDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *ServiceCategoryTranslationDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *ServiceCategoryTranslationDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(servicecategorytranslation.Table, nil) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// ServiceCategoryTranslationDeleteOne is the builder for deleting a single ServiceCategoryTranslation entity. +type ServiceCategoryTranslationDeleteOne struct { + _d *ServiceCategoryTranslationDelete +} + +// Where appends a list predicates to the ServiceCategoryTranslationDelete builder. +func (_d *ServiceCategoryTranslationDeleteOne) Where(ps ...predicate.ServiceCategoryTranslation) *ServiceCategoryTranslationDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *ServiceCategoryTranslationDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{servicecategorytranslation.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *ServiceCategoryTranslationDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/servicecategorytranslation_query.go b/backend/ent/servicecategorytranslation_query.go new file mode 100644 index 0000000..7d9a8fe --- /dev/null +++ b/backend/ent/servicecategorytranslation_query.go @@ -0,0 +1,604 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategorytranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" + "github.com/google/uuid" +) + +// ServiceCategoryTranslationQuery is the builder for querying ServiceCategoryTranslation entities. +type ServiceCategoryTranslationQuery struct { + config + ctx *QueryContext + order []servicecategorytranslation.OrderOption + inters []Interceptor + predicates []predicate.ServiceCategoryTranslation + withCategory *ServiceCategoryQuery + withLocaleRecord *SupportedLocaleQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the ServiceCategoryTranslationQuery builder. +func (_q *ServiceCategoryTranslationQuery) Where(ps ...predicate.ServiceCategoryTranslation) *ServiceCategoryTranslationQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *ServiceCategoryTranslationQuery) Limit(limit int) *ServiceCategoryTranslationQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *ServiceCategoryTranslationQuery) Offset(offset int) *ServiceCategoryTranslationQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *ServiceCategoryTranslationQuery) Unique(unique bool) *ServiceCategoryTranslationQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *ServiceCategoryTranslationQuery) Order(o ...servicecategorytranslation.OrderOption) *ServiceCategoryTranslationQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QueryCategory chains the current query on the "category" edge. +func (_q *ServiceCategoryTranslationQuery) QueryCategory() *ServiceCategoryQuery { + query := (&ServiceCategoryClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(servicecategorytranslation.Table, servicecategorytranslation.CategoryColumn, selector), + sqlgraph.To(servicecategory.Table, servicecategory.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, servicecategorytranslation.CategoryTable, servicecategorytranslation.CategoryColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryLocaleRecord chains the current query on the "locale_record" edge. +func (_q *ServiceCategoryTranslationQuery) QueryLocaleRecord() *SupportedLocaleQuery { + query := (&SupportedLocaleClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(servicecategorytranslation.Table, servicecategorytranslation.LocaleRecordColumn, selector), + sqlgraph.To(supportedlocale.Table, supportedlocale.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, servicecategorytranslation.LocaleRecordTable, servicecategorytranslation.LocaleRecordColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first ServiceCategoryTranslation entity from the query. +// Returns a *NotFoundError when no ServiceCategoryTranslation was found. +func (_q *ServiceCategoryTranslationQuery) First(ctx context.Context) (*ServiceCategoryTranslation, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{servicecategorytranslation.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *ServiceCategoryTranslationQuery) FirstX(ctx context.Context) *ServiceCategoryTranslation { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// Only returns a single ServiceCategoryTranslation entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one ServiceCategoryTranslation entity is found. +// Returns a *NotFoundError when no ServiceCategoryTranslation entities are found. +func (_q *ServiceCategoryTranslationQuery) Only(ctx context.Context) (*ServiceCategoryTranslation, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{servicecategorytranslation.Label} + default: + return nil, &NotSingularError{servicecategorytranslation.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *ServiceCategoryTranslationQuery) OnlyX(ctx context.Context) *ServiceCategoryTranslation { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// All executes the query and returns a list of ServiceCategoryTranslations. +func (_q *ServiceCategoryTranslationQuery) All(ctx context.Context) ([]*ServiceCategoryTranslation, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*ServiceCategoryTranslation, *ServiceCategoryTranslationQuery]() + return withInterceptors[[]*ServiceCategoryTranslation](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *ServiceCategoryTranslationQuery) AllX(ctx context.Context) []*ServiceCategoryTranslation { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// Count returns the count of the given query. +func (_q *ServiceCategoryTranslationQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*ServiceCategoryTranslationQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *ServiceCategoryTranslationQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *ServiceCategoryTranslationQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.First(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *ServiceCategoryTranslationQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the ServiceCategoryTranslationQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *ServiceCategoryTranslationQuery) Clone() *ServiceCategoryTranslationQuery { + if _q == nil { + return nil + } + return &ServiceCategoryTranslationQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]servicecategorytranslation.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.ServiceCategoryTranslation{}, _q.predicates...), + withCategory: _q.withCategory.Clone(), + withLocaleRecord: _q.withLocaleRecord.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// WithCategory tells the query-builder to eager-load the nodes that are connected to +// the "category" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ServiceCategoryTranslationQuery) WithCategory(opts ...func(*ServiceCategoryQuery)) *ServiceCategoryTranslationQuery { + query := (&ServiceCategoryClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withCategory = query + return _q +} + +// WithLocaleRecord tells the query-builder to eager-load the nodes that are connected to +// the "locale_record" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *ServiceCategoryTranslationQuery) WithLocaleRecord(opts ...func(*SupportedLocaleQuery)) *ServiceCategoryTranslationQuery { + query := (&SupportedLocaleClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withLocaleRecord = query + return _q +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// CategoryID uuid.UUID `json:"category_id,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.ServiceCategoryTranslation.Query(). +// GroupBy(servicecategorytranslation.FieldCategoryID). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *ServiceCategoryTranslationQuery) GroupBy(field string, fields ...string) *ServiceCategoryTranslationGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &ServiceCategoryTranslationGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = servicecategorytranslation.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// CategoryID uuid.UUID `json:"category_id,omitempty"` +// } +// +// client.ServiceCategoryTranslation.Query(). +// Select(servicecategorytranslation.FieldCategoryID). +// Scan(ctx, &v) +func (_q *ServiceCategoryTranslationQuery) Select(fields ...string) *ServiceCategoryTranslationSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &ServiceCategoryTranslationSelect{ServiceCategoryTranslationQuery: _q} + sbuild.label = servicecategorytranslation.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a ServiceCategoryTranslationSelect configured with the given aggregations. +func (_q *ServiceCategoryTranslationQuery) Aggregate(fns ...AggregateFunc) *ServiceCategoryTranslationSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *ServiceCategoryTranslationQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !servicecategorytranslation.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *ServiceCategoryTranslationQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ServiceCategoryTranslation, error) { + var ( + nodes = []*ServiceCategoryTranslation{} + _spec = _q.querySpec() + loadedTypes = [2]bool{ + _q.withCategory != nil, + _q.withLocaleRecord != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*ServiceCategoryTranslation).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &ServiceCategoryTranslation{config: _q.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := _q.withCategory; query != nil { + if err := _q.loadCategory(ctx, query, nodes, nil, + func(n *ServiceCategoryTranslation, e *ServiceCategory) { n.Edges.Category = e }); err != nil { + return nil, err + } + } + if query := _q.withLocaleRecord; query != nil { + if err := _q.loadLocaleRecord(ctx, query, nodes, nil, + func(n *ServiceCategoryTranslation, e *SupportedLocale) { n.Edges.LocaleRecord = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *ServiceCategoryTranslationQuery) loadCategory(ctx context.Context, query *ServiceCategoryQuery, nodes []*ServiceCategoryTranslation, init func(*ServiceCategoryTranslation), assign func(*ServiceCategoryTranslation, *ServiceCategory)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*ServiceCategoryTranslation) + for i := range nodes { + fk := nodes[i].CategoryID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(servicecategory.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "category_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (_q *ServiceCategoryTranslationQuery) loadLocaleRecord(ctx context.Context, query *SupportedLocaleQuery, nodes []*ServiceCategoryTranslation, init func(*ServiceCategoryTranslation), assign func(*ServiceCategoryTranslation, *SupportedLocale)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*ServiceCategoryTranslation) + for i := range nodes { + fk := nodes[i].Locale + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(supportedlocale.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "locale" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (_q *ServiceCategoryTranslationQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Unique = false + _spec.Node.Columns = nil + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *ServiceCategoryTranslationQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(servicecategorytranslation.Table, servicecategorytranslation.Columns, nil) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + for i := range fields { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + if _q.withCategory != nil { + _spec.Node.AddColumnOnce(servicecategorytranslation.FieldCategoryID) + } + if _q.withLocaleRecord != nil { + _spec.Node.AddColumnOnce(servicecategorytranslation.FieldLocale) + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *ServiceCategoryTranslationQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(servicecategorytranslation.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = servicecategorytranslation.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ServiceCategoryTranslationGroupBy is the group-by builder for ServiceCategoryTranslation entities. +type ServiceCategoryTranslationGroupBy struct { + selector + build *ServiceCategoryTranslationQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *ServiceCategoryTranslationGroupBy) Aggregate(fns ...AggregateFunc) *ServiceCategoryTranslationGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *ServiceCategoryTranslationGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ServiceCategoryTranslationQuery, *ServiceCategoryTranslationGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *ServiceCategoryTranslationGroupBy) sqlScan(ctx context.Context, root *ServiceCategoryTranslationQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// ServiceCategoryTranslationSelect is the builder for selecting fields of ServiceCategoryTranslation entities. +type ServiceCategoryTranslationSelect struct { + *ServiceCategoryTranslationQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *ServiceCategoryTranslationSelect) Aggregate(fns ...AggregateFunc) *ServiceCategoryTranslationSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *ServiceCategoryTranslationSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ServiceCategoryTranslationQuery, *ServiceCategoryTranslationSelect](ctx, _s.ServiceCategoryTranslationQuery, _s, _s.inters, v) +} + +func (_s *ServiceCategoryTranslationSelect) sqlScan(ctx context.Context, root *ServiceCategoryTranslationQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/servicecategorytranslation_update.go b/backend/ent/servicecategorytranslation_update.go new file mode 100644 index 0000000..38e6974 --- /dev/null +++ b/backend/ent/servicecategorytranslation_update.go @@ -0,0 +1,311 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategorytranslation" +) + +// ServiceCategoryTranslationUpdate is the builder for updating ServiceCategoryTranslation entities. +type ServiceCategoryTranslationUpdate struct { + config + hooks []Hook + mutation *ServiceCategoryTranslationMutation +} + +// Where appends a list predicates to the ServiceCategoryTranslationUpdate builder. +func (_u *ServiceCategoryTranslationUpdate) Where(ps ...predicate.ServiceCategoryTranslation) *ServiceCategoryTranslationUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetName sets the "name" field. +func (_u *ServiceCategoryTranslationUpdate) SetName(v string) *ServiceCategoryTranslationUpdate { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *ServiceCategoryTranslationUpdate) SetNillableName(v *string) *ServiceCategoryTranslationUpdate { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetDescription sets the "description" field. +func (_u *ServiceCategoryTranslationUpdate) SetDescription(v string) *ServiceCategoryTranslationUpdate { + _u.mutation.SetDescription(v) + return _u +} + +// SetNillableDescription sets the "description" field if the given value is not nil. +func (_u *ServiceCategoryTranslationUpdate) SetNillableDescription(v *string) *ServiceCategoryTranslationUpdate { + if v != nil { + _u.SetDescription(*v) + } + return _u +} + +// ClearDescription clears the value of the "description" field. +func (_u *ServiceCategoryTranslationUpdate) ClearDescription() *ServiceCategoryTranslationUpdate { + _u.mutation.ClearDescription() + return _u +} + +// Mutation returns the ServiceCategoryTranslationMutation object of the builder. +func (_u *ServiceCategoryTranslationUpdate) Mutation() *ServiceCategoryTranslationMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *ServiceCategoryTranslationUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *ServiceCategoryTranslationUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *ServiceCategoryTranslationUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *ServiceCategoryTranslationUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *ServiceCategoryTranslationUpdate) check() error { + if v, ok := _u.mutation.Name(); ok { + if err := servicecategorytranslation.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "ServiceCategoryTranslation.name": %w`, err)} + } + } + if v, ok := _u.mutation.Description(); ok { + if err := servicecategorytranslation.DescriptionValidator(v); err != nil { + return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "ServiceCategoryTranslation.description": %w`, err)} + } + } + if _u.mutation.CategoryCleared() && len(_u.mutation.CategoryIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "ServiceCategoryTranslation.category"`) + } + if _u.mutation.LocaleRecordCleared() && len(_u.mutation.LocaleRecordIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "ServiceCategoryTranslation.locale_record"`) + } + return nil +} + +func (_u *ServiceCategoryTranslationUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(servicecategorytranslation.Table, servicecategorytranslation.Columns, sqlgraph.NewFieldSpec(servicecategorytranslation.FieldCategoryID, field.TypeUUID), sqlgraph.NewFieldSpec(servicecategorytranslation.FieldLocale, field.TypeString)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(servicecategorytranslation.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Description(); ok { + _spec.SetField(servicecategorytranslation.FieldDescription, field.TypeString, value) + } + if _u.mutation.DescriptionCleared() { + _spec.ClearField(servicecategorytranslation.FieldDescription, field.TypeString) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{servicecategorytranslation.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// ServiceCategoryTranslationUpdateOne is the builder for updating a single ServiceCategoryTranslation entity. +type ServiceCategoryTranslationUpdateOne struct { + config + fields []string + hooks []Hook + mutation *ServiceCategoryTranslationMutation +} + +// SetName sets the "name" field. +func (_u *ServiceCategoryTranslationUpdateOne) SetName(v string) *ServiceCategoryTranslationUpdateOne { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *ServiceCategoryTranslationUpdateOne) SetNillableName(v *string) *ServiceCategoryTranslationUpdateOne { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetDescription sets the "description" field. +func (_u *ServiceCategoryTranslationUpdateOne) SetDescription(v string) *ServiceCategoryTranslationUpdateOne { + _u.mutation.SetDescription(v) + return _u +} + +// SetNillableDescription sets the "description" field if the given value is not nil. +func (_u *ServiceCategoryTranslationUpdateOne) SetNillableDescription(v *string) *ServiceCategoryTranslationUpdateOne { + if v != nil { + _u.SetDescription(*v) + } + return _u +} + +// ClearDescription clears the value of the "description" field. +func (_u *ServiceCategoryTranslationUpdateOne) ClearDescription() *ServiceCategoryTranslationUpdateOne { + _u.mutation.ClearDescription() + return _u +} + +// Mutation returns the ServiceCategoryTranslationMutation object of the builder. +func (_u *ServiceCategoryTranslationUpdateOne) Mutation() *ServiceCategoryTranslationMutation { + return _u.mutation +} + +// Where appends a list predicates to the ServiceCategoryTranslationUpdate builder. +func (_u *ServiceCategoryTranslationUpdateOne) Where(ps ...predicate.ServiceCategoryTranslation) *ServiceCategoryTranslationUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *ServiceCategoryTranslationUpdateOne) Select(field string, fields ...string) *ServiceCategoryTranslationUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated ServiceCategoryTranslation entity. +func (_u *ServiceCategoryTranslationUpdateOne) Save(ctx context.Context) (*ServiceCategoryTranslation, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *ServiceCategoryTranslationUpdateOne) SaveX(ctx context.Context) *ServiceCategoryTranslation { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *ServiceCategoryTranslationUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *ServiceCategoryTranslationUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *ServiceCategoryTranslationUpdateOne) check() error { + if v, ok := _u.mutation.Name(); ok { + if err := servicecategorytranslation.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "ServiceCategoryTranslation.name": %w`, err)} + } + } + if v, ok := _u.mutation.Description(); ok { + if err := servicecategorytranslation.DescriptionValidator(v); err != nil { + return &ValidationError{Name: "description", err: fmt.Errorf(`ent: validator failed for field "ServiceCategoryTranslation.description": %w`, err)} + } + } + if _u.mutation.CategoryCleared() && len(_u.mutation.CategoryIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "ServiceCategoryTranslation.category"`) + } + if _u.mutation.LocaleRecordCleared() && len(_u.mutation.LocaleRecordIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "ServiceCategoryTranslation.locale_record"`) + } + return nil +} + +func (_u *ServiceCategoryTranslationUpdateOne) sqlSave(ctx context.Context) (_node *ServiceCategoryTranslation, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(servicecategorytranslation.Table, servicecategorytranslation.Columns, sqlgraph.NewFieldSpec(servicecategorytranslation.FieldCategoryID, field.TypeUUID), sqlgraph.NewFieldSpec(servicecategorytranslation.FieldLocale, field.TypeString)) + if id, ok := _u.mutation.CategoryID(); !ok { + return nil, &ValidationError{Name: "category_id", err: errors.New(`ent: missing "ServiceCategoryTranslation.category_id" for update`)} + } else { + _spec.Node.CompositeID[0].Value = id + } + if id, ok := _u.mutation.Locale(); !ok { + return nil, &ValidationError{Name: "locale", err: errors.New(`ent: missing "ServiceCategoryTranslation.locale" for update`)} + } else { + _spec.Node.CompositeID[1].Value = id + } + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, len(fields)) + for i, f := range fields { + if !servicecategorytranslation.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + _spec.Node.Columns[i] = f + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(servicecategorytranslation.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Description(); ok { + _spec.SetField(servicecategorytranslation.FieldDescription, field.TypeString, value) + } + if _u.mutation.DescriptionCleared() { + _spec.ClearField(servicecategorytranslation.FieldDescription, field.TypeString) + } + _node = &ServiceCategoryTranslation{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{servicecategorytranslation.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/ent/spokenlanguage.go b/backend/ent/spokenlanguage.go new file mode 100644 index 0000000..1819b6c --- /dev/null +++ b/backend/ent/spokenlanguage.go @@ -0,0 +1,174 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" +) + +// SpokenLanguage is the model entity for the SpokenLanguage schema. +type SpokenLanguage struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // Active holds the value of the "active" field. + Active bool `json:"active,omitempty"` + // SortOrder holds the value of the "sort_order" field. + SortOrder int `json:"sort_order,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the SpokenLanguageQuery when eager-loading is set. + Edges SpokenLanguageEdges `json:"edges"` + selectValues sql.SelectValues +} + +// SpokenLanguageEdges holds the relations/edges for other nodes in the graph. +type SpokenLanguageEdges struct { + // LocalizedIn holds the value of the localized_in edge. + LocalizedIn []*SupportedLocale `json:"localized_in,omitempty"` + // ProviderProfiles holds the value of the provider_profiles edge. + ProviderProfiles []*ProviderProfile `json:"provider_profiles,omitempty"` + // Translations holds the value of the translations edge. + Translations []*SpokenLanguageTranslation `json:"translations,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [3]bool +} + +// LocalizedInOrErr returns the LocalizedIn value or an error if the edge +// was not loaded in eager-loading. +func (e SpokenLanguageEdges) LocalizedInOrErr() ([]*SupportedLocale, error) { + if e.loadedTypes[0] { + return e.LocalizedIn, nil + } + return nil, &NotLoadedError{edge: "localized_in"} +} + +// ProviderProfilesOrErr returns the ProviderProfiles value or an error if the edge +// was not loaded in eager-loading. +func (e SpokenLanguageEdges) ProviderProfilesOrErr() ([]*ProviderProfile, error) { + if e.loadedTypes[1] { + return e.ProviderProfiles, nil + } + return nil, &NotLoadedError{edge: "provider_profiles"} +} + +// TranslationsOrErr returns the Translations value or an error if the edge +// was not loaded in eager-loading. +func (e SpokenLanguageEdges) TranslationsOrErr() ([]*SpokenLanguageTranslation, error) { + if e.loadedTypes[2] { + return e.Translations, nil + } + return nil, &NotLoadedError{edge: "translations"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*SpokenLanguage) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case spokenlanguage.FieldActive: + values[i] = new(sql.NullBool) + case spokenlanguage.FieldSortOrder: + values[i] = new(sql.NullInt64) + case spokenlanguage.FieldID: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the SpokenLanguage fields. +func (_m *SpokenLanguage) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case spokenlanguage.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + _m.ID = value.String + } + case spokenlanguage.FieldActive: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field active", values[i]) + } else if value.Valid { + _m.Active = value.Bool + } + case spokenlanguage.FieldSortOrder: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field sort_order", values[i]) + } else if value.Valid { + _m.SortOrder = int(value.Int64) + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the SpokenLanguage. +// This includes values selected through modifiers, order, etc. +func (_m *SpokenLanguage) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QueryLocalizedIn queries the "localized_in" edge of the SpokenLanguage entity. +func (_m *SpokenLanguage) QueryLocalizedIn() *SupportedLocaleQuery { + return NewSpokenLanguageClient(_m.config).QueryLocalizedIn(_m) +} + +// QueryProviderProfiles queries the "provider_profiles" edge of the SpokenLanguage entity. +func (_m *SpokenLanguage) QueryProviderProfiles() *ProviderProfileQuery { + return NewSpokenLanguageClient(_m.config).QueryProviderProfiles(_m) +} + +// QueryTranslations queries the "translations" edge of the SpokenLanguage entity. +func (_m *SpokenLanguage) QueryTranslations() *SpokenLanguageTranslationQuery { + return NewSpokenLanguageClient(_m.config).QueryTranslations(_m) +} + +// Update returns a builder for updating this SpokenLanguage. +// Note that you need to call SpokenLanguage.Unwrap() before calling this method if this SpokenLanguage +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *SpokenLanguage) Update() *SpokenLanguageUpdateOne { + return NewSpokenLanguageClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the SpokenLanguage entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *SpokenLanguage) Unwrap() *SpokenLanguage { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: SpokenLanguage is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *SpokenLanguage) String() string { + var builder strings.Builder + builder.WriteString("SpokenLanguage(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("active=") + builder.WriteString(fmt.Sprintf("%v", _m.Active)) + builder.WriteString(", ") + builder.WriteString("sort_order=") + builder.WriteString(fmt.Sprintf("%v", _m.SortOrder)) + builder.WriteByte(')') + return builder.String() +} + +// SpokenLanguages is a parsable slice of SpokenLanguage. +type SpokenLanguages []*SpokenLanguage diff --git a/backend/ent/spokenlanguage/spokenlanguage.go b/backend/ent/spokenlanguage/spokenlanguage.go new file mode 100644 index 0000000..e83767d --- /dev/null +++ b/backend/ent/spokenlanguage/spokenlanguage.go @@ -0,0 +1,162 @@ +// Code generated by ent, DO NOT EDIT. + +package spokenlanguage + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the spokenlanguage type in the database. + Label = "spoken_language" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldActive holds the string denoting the active field in the database. + FieldActive = "active" + // FieldSortOrder holds the string denoting the sort_order field in the database. + FieldSortOrder = "sort_order" + // EdgeLocalizedIn holds the string denoting the localized_in edge name in mutations. + EdgeLocalizedIn = "localized_in" + // EdgeProviderProfiles holds the string denoting the provider_profiles edge name in mutations. + EdgeProviderProfiles = "provider_profiles" + // EdgeTranslations holds the string denoting the translations edge name in mutations. + EdgeTranslations = "translations" + // ProviderProfileFieldID holds the string denoting the ID field of the ProviderProfile. + ProviderProfileFieldID = "internal_user_id" + // Table holds the table name of the spokenlanguage in the database. + Table = "spoken_languages" + // LocalizedInTable is the table that holds the localized_in relation/edge. The primary key declared below. + LocalizedInTable = "spoken_language_translations" + // LocalizedInInverseTable is the table name for the SupportedLocale entity. + // It exists in this package in order to avoid circular dependency with the "supportedlocale" package. + LocalizedInInverseTable = "supported_locales" + // ProviderProfilesTable is the table that holds the provider_profiles relation/edge. The primary key declared below. + ProviderProfilesTable = "provider_spoken_languages" + // ProviderProfilesInverseTable is the table name for the ProviderProfile entity. + // It exists in this package in order to avoid circular dependency with the "providerprofile" package. + ProviderProfilesInverseTable = "provider_profiles" + // TranslationsTable is the table that holds the translations relation/edge. + TranslationsTable = "spoken_language_translations" + // TranslationsInverseTable is the table name for the SpokenLanguageTranslation entity. + // It exists in this package in order to avoid circular dependency with the "spokenlanguagetranslation" package. + TranslationsInverseTable = "spoken_language_translations" + // TranslationsColumn is the table column denoting the translations relation/edge. + TranslationsColumn = "language_code" +) + +// Columns holds all SQL columns for spokenlanguage fields. +var Columns = []string{ + FieldID, + FieldActive, + FieldSortOrder, +} + +var ( + // LocalizedInPrimaryKey and LocalizedInColumn2 are the table columns denoting the + // primary key for the localized_in relation (M2M). + LocalizedInPrimaryKey = []string{"language_code", "locale"} + // ProviderProfilesPrimaryKey and ProviderProfilesColumn2 are the table columns denoting the + // primary key for the provider_profiles relation (M2M). + ProviderProfilesPrimaryKey = []string{"internal_user_id", "language_code"} +) + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultActive holds the default value on creation for the "active" field. + DefaultActive bool + // SortOrderValidator is a validator for the "sort_order" field. It is called by the builders before save. + SortOrderValidator func(int) error + // IDValidator is a validator for the "id" field. It is called by the builders before save. + IDValidator func(string) error +) + +// OrderOption defines the ordering options for the SpokenLanguage queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByActive orders the results by the active field. +func ByActive(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldActive, opts...).ToFunc() +} + +// BySortOrder orders the results by the sort_order field. +func BySortOrder(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSortOrder, opts...).ToFunc() +} + +// ByLocalizedInCount orders the results by localized_in count. +func ByLocalizedInCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newLocalizedInStep(), opts...) + } +} + +// ByLocalizedIn orders the results by localized_in terms. +func ByLocalizedIn(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newLocalizedInStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByProviderProfilesCount orders the results by provider_profiles count. +func ByProviderProfilesCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newProviderProfilesStep(), opts...) + } +} + +// ByProviderProfiles orders the results by provider_profiles terms. +func ByProviderProfiles(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newProviderProfilesStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByTranslationsCount orders the results by translations count. +func ByTranslationsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newTranslationsStep(), opts...) + } +} + +// ByTranslations orders the results by translations terms. +func ByTranslations(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newTranslationsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} +func newLocalizedInStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(LocalizedInInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, LocalizedInTable, LocalizedInPrimaryKey...), + ) +} +func newProviderProfilesStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ProviderProfilesInverseTable, ProviderProfileFieldID), + sqlgraph.Edge(sqlgraph.M2M, true, ProviderProfilesTable, ProviderProfilesPrimaryKey...), + ) +} +func newTranslationsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(TranslationsInverseTable, TranslationsColumn), + sqlgraph.Edge(sqlgraph.O2M, true, TranslationsTable, TranslationsColumn), + ) +} diff --git a/backend/ent/spokenlanguage/where.go b/backend/ent/spokenlanguage/where.go new file mode 100644 index 0000000..5d092bd --- /dev/null +++ b/backend/ent/spokenlanguage/where.go @@ -0,0 +1,208 @@ +// Code generated by ent, DO NOT EDIT. + +package spokenlanguage + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldContainsFold(FieldID, id)) +} + +// Active applies equality check predicate on the "active" field. It's identical to ActiveEQ. +func Active(v bool) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldEQ(FieldActive, v)) +} + +// SortOrder applies equality check predicate on the "sort_order" field. It's identical to SortOrderEQ. +func SortOrder(v int) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldEQ(FieldSortOrder, v)) +} + +// ActiveEQ applies the EQ predicate on the "active" field. +func ActiveEQ(v bool) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldEQ(FieldActive, v)) +} + +// ActiveNEQ applies the NEQ predicate on the "active" field. +func ActiveNEQ(v bool) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldNEQ(FieldActive, v)) +} + +// SortOrderEQ applies the EQ predicate on the "sort_order" field. +func SortOrderEQ(v int) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldEQ(FieldSortOrder, v)) +} + +// SortOrderNEQ applies the NEQ predicate on the "sort_order" field. +func SortOrderNEQ(v int) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldNEQ(FieldSortOrder, v)) +} + +// SortOrderIn applies the In predicate on the "sort_order" field. +func SortOrderIn(vs ...int) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldIn(FieldSortOrder, vs...)) +} + +// SortOrderNotIn applies the NotIn predicate on the "sort_order" field. +func SortOrderNotIn(vs ...int) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldNotIn(FieldSortOrder, vs...)) +} + +// SortOrderGT applies the GT predicate on the "sort_order" field. +func SortOrderGT(v int) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldGT(FieldSortOrder, v)) +} + +// SortOrderGTE applies the GTE predicate on the "sort_order" field. +func SortOrderGTE(v int) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldGTE(FieldSortOrder, v)) +} + +// SortOrderLT applies the LT predicate on the "sort_order" field. +func SortOrderLT(v int) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldLT(FieldSortOrder, v)) +} + +// SortOrderLTE applies the LTE predicate on the "sort_order" field. +func SortOrderLTE(v int) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.FieldLTE(FieldSortOrder, v)) +} + +// HasLocalizedIn applies the HasEdge predicate on the "localized_in" edge. +func HasLocalizedIn() predicate.SpokenLanguage { + return predicate.SpokenLanguage(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, LocalizedInTable, LocalizedInPrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasLocalizedInWith applies the HasEdge predicate on the "localized_in" edge with a given conditions (other predicates). +func HasLocalizedInWith(preds ...predicate.SupportedLocale) predicate.SpokenLanguage { + return predicate.SpokenLanguage(func(s *sql.Selector) { + step := newLocalizedInStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasProviderProfiles applies the HasEdge predicate on the "provider_profiles" edge. +func HasProviderProfiles() predicate.SpokenLanguage { + return predicate.SpokenLanguage(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, ProviderProfilesTable, ProviderProfilesPrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasProviderProfilesWith applies the HasEdge predicate on the "provider_profiles" edge with a given conditions (other predicates). +func HasProviderProfilesWith(preds ...predicate.ProviderProfile) predicate.SpokenLanguage { + return predicate.SpokenLanguage(func(s *sql.Selector) { + step := newProviderProfilesStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasTranslations applies the HasEdge predicate on the "translations" edge. +func HasTranslations() predicate.SpokenLanguage { + return predicate.SpokenLanguage(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, true, TranslationsTable, TranslationsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasTranslationsWith applies the HasEdge predicate on the "translations" edge with a given conditions (other predicates). +func HasTranslationsWith(preds ...predicate.SpokenLanguageTranslation) predicate.SpokenLanguage { + return predicate.SpokenLanguage(func(s *sql.Selector) { + step := newTranslationsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.SpokenLanguage) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.SpokenLanguage) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.SpokenLanguage) predicate.SpokenLanguage { + return predicate.SpokenLanguage(sql.NotPredicates(p)) +} diff --git a/backend/ent/spokenlanguage_create.go b/backend/ent/spokenlanguage_create.go new file mode 100644 index 0000000..4e27c19 --- /dev/null +++ b/backend/ent/spokenlanguage_create.go @@ -0,0 +1,300 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" + "github.com/google/uuid" +) + +// SpokenLanguageCreate is the builder for creating a SpokenLanguage entity. +type SpokenLanguageCreate struct { + config + mutation *SpokenLanguageMutation + hooks []Hook +} + +// SetActive sets the "active" field. +func (_c *SpokenLanguageCreate) SetActive(v bool) *SpokenLanguageCreate { + _c.mutation.SetActive(v) + return _c +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_c *SpokenLanguageCreate) SetNillableActive(v *bool) *SpokenLanguageCreate { + if v != nil { + _c.SetActive(*v) + } + return _c +} + +// SetSortOrder sets the "sort_order" field. +func (_c *SpokenLanguageCreate) SetSortOrder(v int) *SpokenLanguageCreate { + _c.mutation.SetSortOrder(v) + return _c +} + +// SetID sets the "id" field. +func (_c *SpokenLanguageCreate) SetID(v string) *SpokenLanguageCreate { + _c.mutation.SetID(v) + return _c +} + +// AddLocalizedInIDs adds the "localized_in" edge to the SupportedLocale entity by IDs. +func (_c *SpokenLanguageCreate) AddLocalizedInIDs(ids ...string) *SpokenLanguageCreate { + _c.mutation.AddLocalizedInIDs(ids...) + return _c +} + +// AddLocalizedIn adds the "localized_in" edges to the SupportedLocale entity. +func (_c *SpokenLanguageCreate) AddLocalizedIn(v ...*SupportedLocale) *SpokenLanguageCreate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddLocalizedInIDs(ids...) +} + +// AddProviderProfileIDs adds the "provider_profiles" edge to the ProviderProfile entity by IDs. +func (_c *SpokenLanguageCreate) AddProviderProfileIDs(ids ...uuid.UUID) *SpokenLanguageCreate { + _c.mutation.AddProviderProfileIDs(ids...) + return _c +} + +// AddProviderProfiles adds the "provider_profiles" edges to the ProviderProfile entity. +func (_c *SpokenLanguageCreate) AddProviderProfiles(v ...*ProviderProfile) *SpokenLanguageCreate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddProviderProfileIDs(ids...) +} + +// Mutation returns the SpokenLanguageMutation object of the builder. +func (_c *SpokenLanguageCreate) Mutation() *SpokenLanguageMutation { + return _c.mutation +} + +// Save creates the SpokenLanguage in the database. +func (_c *SpokenLanguageCreate) Save(ctx context.Context) (*SpokenLanguage, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *SpokenLanguageCreate) SaveX(ctx context.Context) *SpokenLanguage { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *SpokenLanguageCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *SpokenLanguageCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *SpokenLanguageCreate) defaults() { + if _, ok := _c.mutation.Active(); !ok { + v := spokenlanguage.DefaultActive + _c.mutation.SetActive(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *SpokenLanguageCreate) check() error { + if _, ok := _c.mutation.Active(); !ok { + return &ValidationError{Name: "active", err: errors.New(`ent: missing required field "SpokenLanguage.active"`)} + } + if _, ok := _c.mutation.SortOrder(); !ok { + return &ValidationError{Name: "sort_order", err: errors.New(`ent: missing required field "SpokenLanguage.sort_order"`)} + } + if v, ok := _c.mutation.SortOrder(); ok { + if err := spokenlanguage.SortOrderValidator(v); err != nil { + return &ValidationError{Name: "sort_order", err: fmt.Errorf(`ent: validator failed for field "SpokenLanguage.sort_order": %w`, err)} + } + } + if v, ok := _c.mutation.ID(); ok { + if err := spokenlanguage.IDValidator(v); err != nil { + return &ValidationError{Name: "id", err: fmt.Errorf(`ent: validator failed for field "SpokenLanguage.id": %w`, err)} + } + } + return nil +} + +func (_c *SpokenLanguageCreate) sqlSave(ctx context.Context) (*SpokenLanguage, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected SpokenLanguage.ID type: %T", _spec.ID.Value) + } + } + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *SpokenLanguageCreate) createSpec() (*SpokenLanguage, *sqlgraph.CreateSpec) { + var ( + _node = &SpokenLanguage{config: _c.config} + _spec = sqlgraph.NewCreateSpec(spokenlanguage.Table, sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString)) + ) + if id, ok := _c.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := _c.mutation.Active(); ok { + _spec.SetField(spokenlanguage.FieldActive, field.TypeBool, value) + _node.Active = value + } + if value, ok := _c.mutation.SortOrder(); ok { + _spec.SetField(spokenlanguage.FieldSortOrder, field.TypeInt, value) + _node.SortOrder = value + } + if nodes := _c.mutation.LocalizedInIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: spokenlanguage.LocalizedInTable, + Columns: spokenlanguage.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.ProviderProfilesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: spokenlanguage.ProviderProfilesTable, + Columns: spokenlanguage.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// SpokenLanguageCreateBulk is the builder for creating many SpokenLanguage entities in bulk. +type SpokenLanguageCreateBulk struct { + config + err error + builders []*SpokenLanguageCreate +} + +// Save creates the SpokenLanguage entities in the database. +func (_c *SpokenLanguageCreateBulk) Save(ctx context.Context) ([]*SpokenLanguage, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*SpokenLanguage, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*SpokenLanguageMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *SpokenLanguageCreateBulk) SaveX(ctx context.Context) []*SpokenLanguage { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *SpokenLanguageCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *SpokenLanguageCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/spokenlanguage_delete.go b/backend/ent/spokenlanguage_delete.go new file mode 100644 index 0000000..f5e9e75 --- /dev/null +++ b/backend/ent/spokenlanguage_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" +) + +// SpokenLanguageDelete is the builder for deleting a SpokenLanguage entity. +type SpokenLanguageDelete struct { + config + hooks []Hook + mutation *SpokenLanguageMutation +} + +// Where appends a list predicates to the SpokenLanguageDelete builder. +func (_d *SpokenLanguageDelete) Where(ps ...predicate.SpokenLanguage) *SpokenLanguageDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *SpokenLanguageDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *SpokenLanguageDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *SpokenLanguageDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(spokenlanguage.Table, sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// SpokenLanguageDeleteOne is the builder for deleting a single SpokenLanguage entity. +type SpokenLanguageDeleteOne struct { + _d *SpokenLanguageDelete +} + +// Where appends a list predicates to the SpokenLanguageDelete builder. +func (_d *SpokenLanguageDeleteOne) Where(ps ...predicate.SpokenLanguage) *SpokenLanguageDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *SpokenLanguageDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{spokenlanguage.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *SpokenLanguageDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/spokenlanguage_query.go b/backend/ent/spokenlanguage_query.go new file mode 100644 index 0000000..4a4a6e3 --- /dev/null +++ b/backend/ent/spokenlanguage_query.go @@ -0,0 +1,821 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "database/sql/driver" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguagetranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" + "github.com/google/uuid" +) + +// SpokenLanguageQuery is the builder for querying SpokenLanguage entities. +type SpokenLanguageQuery struct { + config + ctx *QueryContext + order []spokenlanguage.OrderOption + inters []Interceptor + predicates []predicate.SpokenLanguage + withLocalizedIn *SupportedLocaleQuery + withProviderProfiles *ProviderProfileQuery + withTranslations *SpokenLanguageTranslationQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the SpokenLanguageQuery builder. +func (_q *SpokenLanguageQuery) Where(ps ...predicate.SpokenLanguage) *SpokenLanguageQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *SpokenLanguageQuery) Limit(limit int) *SpokenLanguageQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *SpokenLanguageQuery) Offset(offset int) *SpokenLanguageQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *SpokenLanguageQuery) Unique(unique bool) *SpokenLanguageQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *SpokenLanguageQuery) Order(o ...spokenlanguage.OrderOption) *SpokenLanguageQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QueryLocalizedIn chains the current query on the "localized_in" edge. +func (_q *SpokenLanguageQuery) QueryLocalizedIn() *SupportedLocaleQuery { + query := (&SupportedLocaleClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(spokenlanguage.Table, spokenlanguage.FieldID, selector), + sqlgraph.To(supportedlocale.Table, supportedlocale.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, spokenlanguage.LocalizedInTable, spokenlanguage.LocalizedInPrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryProviderProfiles chains the current query on the "provider_profiles" edge. +func (_q *SpokenLanguageQuery) QueryProviderProfiles() *ProviderProfileQuery { + query := (&ProviderProfileClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(spokenlanguage.Table, spokenlanguage.FieldID, selector), + sqlgraph.To(providerprofile.Table, providerprofile.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, spokenlanguage.ProviderProfilesTable, spokenlanguage.ProviderProfilesPrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryTranslations chains the current query on the "translations" edge. +func (_q *SpokenLanguageQuery) QueryTranslations() *SpokenLanguageTranslationQuery { + query := (&SpokenLanguageTranslationClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(spokenlanguage.Table, spokenlanguage.FieldID, selector), + sqlgraph.To(spokenlanguagetranslation.Table, spokenlanguagetranslation.LanguageColumn), + sqlgraph.Edge(sqlgraph.O2M, true, spokenlanguage.TranslationsTable, spokenlanguage.TranslationsColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first SpokenLanguage entity from the query. +// Returns a *NotFoundError when no SpokenLanguage was found. +func (_q *SpokenLanguageQuery) First(ctx context.Context) (*SpokenLanguage, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{spokenlanguage.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *SpokenLanguageQuery) FirstX(ctx context.Context) *SpokenLanguage { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first SpokenLanguage ID from the query. +// Returns a *NotFoundError when no SpokenLanguage ID was found. +func (_q *SpokenLanguageQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{spokenlanguage.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *SpokenLanguageQuery) FirstIDX(ctx context.Context) string { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single SpokenLanguage entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one SpokenLanguage entity is found. +// Returns a *NotFoundError when no SpokenLanguage entities are found. +func (_q *SpokenLanguageQuery) Only(ctx context.Context) (*SpokenLanguage, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{spokenlanguage.Label} + default: + return nil, &NotSingularError{spokenlanguage.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *SpokenLanguageQuery) OnlyX(ctx context.Context) *SpokenLanguage { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only SpokenLanguage ID in the query. +// Returns a *NotSingularError when more than one SpokenLanguage ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *SpokenLanguageQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{spokenlanguage.Label} + default: + err = &NotSingularError{spokenlanguage.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *SpokenLanguageQuery) OnlyIDX(ctx context.Context) string { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of SpokenLanguages. +func (_q *SpokenLanguageQuery) All(ctx context.Context) ([]*SpokenLanguage, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*SpokenLanguage, *SpokenLanguageQuery]() + return withInterceptors[[]*SpokenLanguage](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *SpokenLanguageQuery) AllX(ctx context.Context) []*SpokenLanguage { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of SpokenLanguage IDs. +func (_q *SpokenLanguageQuery) IDs(ctx context.Context) (ids []string, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(spokenlanguage.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *SpokenLanguageQuery) IDsX(ctx context.Context) []string { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *SpokenLanguageQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*SpokenLanguageQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *SpokenLanguageQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *SpokenLanguageQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *SpokenLanguageQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the SpokenLanguageQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *SpokenLanguageQuery) Clone() *SpokenLanguageQuery { + if _q == nil { + return nil + } + return &SpokenLanguageQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]spokenlanguage.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.SpokenLanguage{}, _q.predicates...), + withLocalizedIn: _q.withLocalizedIn.Clone(), + withProviderProfiles: _q.withProviderProfiles.Clone(), + withTranslations: _q.withTranslations.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// WithLocalizedIn tells the query-builder to eager-load the nodes that are connected to +// the "localized_in" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *SpokenLanguageQuery) WithLocalizedIn(opts ...func(*SupportedLocaleQuery)) *SpokenLanguageQuery { + query := (&SupportedLocaleClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withLocalizedIn = query + return _q +} + +// WithProviderProfiles tells the query-builder to eager-load the nodes that are connected to +// the "provider_profiles" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *SpokenLanguageQuery) WithProviderProfiles(opts ...func(*ProviderProfileQuery)) *SpokenLanguageQuery { + query := (&ProviderProfileClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withProviderProfiles = query + return _q +} + +// WithTranslations tells the query-builder to eager-load the nodes that are connected to +// the "translations" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *SpokenLanguageQuery) WithTranslations(opts ...func(*SpokenLanguageTranslationQuery)) *SpokenLanguageQuery { + query := (&SpokenLanguageTranslationClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withTranslations = query + return _q +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// Active bool `json:"active,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.SpokenLanguage.Query(). +// GroupBy(spokenlanguage.FieldActive). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *SpokenLanguageQuery) GroupBy(field string, fields ...string) *SpokenLanguageGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &SpokenLanguageGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = spokenlanguage.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// Active bool `json:"active,omitempty"` +// } +// +// client.SpokenLanguage.Query(). +// Select(spokenlanguage.FieldActive). +// Scan(ctx, &v) +func (_q *SpokenLanguageQuery) Select(fields ...string) *SpokenLanguageSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &SpokenLanguageSelect{SpokenLanguageQuery: _q} + sbuild.label = spokenlanguage.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a SpokenLanguageSelect configured with the given aggregations. +func (_q *SpokenLanguageQuery) Aggregate(fns ...AggregateFunc) *SpokenLanguageSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *SpokenLanguageQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !spokenlanguage.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *SpokenLanguageQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*SpokenLanguage, error) { + var ( + nodes = []*SpokenLanguage{} + _spec = _q.querySpec() + loadedTypes = [3]bool{ + _q.withLocalizedIn != nil, + _q.withProviderProfiles != nil, + _q.withTranslations != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*SpokenLanguage).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &SpokenLanguage{config: _q.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := _q.withLocalizedIn; query != nil { + if err := _q.loadLocalizedIn(ctx, query, nodes, + func(n *SpokenLanguage) { n.Edges.LocalizedIn = []*SupportedLocale{} }, + func(n *SpokenLanguage, e *SupportedLocale) { n.Edges.LocalizedIn = append(n.Edges.LocalizedIn, e) }); err != nil { + return nil, err + } + } + if query := _q.withProviderProfiles; query != nil { + if err := _q.loadProviderProfiles(ctx, query, nodes, + func(n *SpokenLanguage) { n.Edges.ProviderProfiles = []*ProviderProfile{} }, + func(n *SpokenLanguage, e *ProviderProfile) { + n.Edges.ProviderProfiles = append(n.Edges.ProviderProfiles, e) + }); err != nil { + return nil, err + } + } + if query := _q.withTranslations; query != nil { + if err := _q.loadTranslations(ctx, query, nodes, + func(n *SpokenLanguage) { n.Edges.Translations = []*SpokenLanguageTranslation{} }, + func(n *SpokenLanguage, e *SpokenLanguageTranslation) { + n.Edges.Translations = append(n.Edges.Translations, e) + }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *SpokenLanguageQuery) loadLocalizedIn(ctx context.Context, query *SupportedLocaleQuery, nodes []*SpokenLanguage, init func(*SpokenLanguage), assign func(*SpokenLanguage, *SupportedLocale)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[string]*SpokenLanguage) + nids := make(map[string]map[*SpokenLanguage]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(spokenlanguage.LocalizedInTable) + s.Join(joinT).On(s.C(supportedlocale.FieldID), joinT.C(spokenlanguage.LocalizedInPrimaryKey[1])) + s.Where(sql.InValues(joinT.C(spokenlanguage.LocalizedInPrimaryKey[0]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(spokenlanguage.LocalizedInPrimaryKey[0])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(sql.NullString)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := values[0].(*sql.NullString).String + inValue := values[1].(*sql.NullString).String + if nids[inValue] == nil { + nids[inValue] = map[*SpokenLanguage]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*SupportedLocale](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "localized_in" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} +func (_q *SpokenLanguageQuery) loadProviderProfiles(ctx context.Context, query *ProviderProfileQuery, nodes []*SpokenLanguage, init func(*SpokenLanguage), assign func(*SpokenLanguage, *ProviderProfile)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[string]*SpokenLanguage) + nids := make(map[uuid.UUID]map[*SpokenLanguage]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(spokenlanguage.ProviderProfilesTable) + s.Join(joinT).On(s.C(providerprofile.FieldID), joinT.C(spokenlanguage.ProviderProfilesPrimaryKey[0])) + s.Where(sql.InValues(joinT.C(spokenlanguage.ProviderProfilesPrimaryKey[1]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(spokenlanguage.ProviderProfilesPrimaryKey[1])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(sql.NullString)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := values[0].(*sql.NullString).String + inValue := *values[1].(*uuid.UUID) + if nids[inValue] == nil { + nids[inValue] = map[*SpokenLanguage]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*ProviderProfile](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "provider_profiles" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} +func (_q *SpokenLanguageQuery) loadTranslations(ctx context.Context, query *SpokenLanguageTranslationQuery, nodes []*SpokenLanguage, init func(*SpokenLanguage), assign func(*SpokenLanguage, *SpokenLanguageTranslation)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*SpokenLanguage) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(spokenlanguagetranslation.FieldLanguageCode) + } + query.Where(predicate.SpokenLanguageTranslation(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(spokenlanguage.TranslationsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.LanguageCode + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "language_code" returned %v for node %v`, fk, n) + } + assign(node, n) + } + return nil +} + +func (_q *SpokenLanguageQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *SpokenLanguageQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(spokenlanguage.Table, spokenlanguage.Columns, sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, spokenlanguage.FieldID) + for i := range fields { + if fields[i] != spokenlanguage.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *SpokenLanguageQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(spokenlanguage.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = spokenlanguage.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// SpokenLanguageGroupBy is the group-by builder for SpokenLanguage entities. +type SpokenLanguageGroupBy struct { + selector + build *SpokenLanguageQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *SpokenLanguageGroupBy) Aggregate(fns ...AggregateFunc) *SpokenLanguageGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *SpokenLanguageGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*SpokenLanguageQuery, *SpokenLanguageGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *SpokenLanguageGroupBy) sqlScan(ctx context.Context, root *SpokenLanguageQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// SpokenLanguageSelect is the builder for selecting fields of SpokenLanguage entities. +type SpokenLanguageSelect struct { + *SpokenLanguageQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *SpokenLanguageSelect) Aggregate(fns ...AggregateFunc) *SpokenLanguageSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *SpokenLanguageSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*SpokenLanguageQuery, *SpokenLanguageSelect](ctx, _s.SpokenLanguageQuery, _s, _s.inters, v) +} + +func (_s *SpokenLanguageSelect) sqlScan(ctx context.Context, root *SpokenLanguageQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/spokenlanguage_update.go b/backend/ent/spokenlanguage_update.go new file mode 100644 index 0000000..e9f19fd --- /dev/null +++ b/backend/ent/spokenlanguage_update.go @@ -0,0 +1,616 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" + "github.com/google/uuid" +) + +// SpokenLanguageUpdate is the builder for updating SpokenLanguage entities. +type SpokenLanguageUpdate struct { + config + hooks []Hook + mutation *SpokenLanguageMutation +} + +// Where appends a list predicates to the SpokenLanguageUpdate builder. +func (_u *SpokenLanguageUpdate) Where(ps ...predicate.SpokenLanguage) *SpokenLanguageUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetActive sets the "active" field. +func (_u *SpokenLanguageUpdate) SetActive(v bool) *SpokenLanguageUpdate { + _u.mutation.SetActive(v) + return _u +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_u *SpokenLanguageUpdate) SetNillableActive(v *bool) *SpokenLanguageUpdate { + if v != nil { + _u.SetActive(*v) + } + return _u +} + +// SetSortOrder sets the "sort_order" field. +func (_u *SpokenLanguageUpdate) SetSortOrder(v int) *SpokenLanguageUpdate { + _u.mutation.ResetSortOrder() + _u.mutation.SetSortOrder(v) + return _u +} + +// SetNillableSortOrder sets the "sort_order" field if the given value is not nil. +func (_u *SpokenLanguageUpdate) SetNillableSortOrder(v *int) *SpokenLanguageUpdate { + if v != nil { + _u.SetSortOrder(*v) + } + return _u +} + +// AddSortOrder adds value to the "sort_order" field. +func (_u *SpokenLanguageUpdate) AddSortOrder(v int) *SpokenLanguageUpdate { + _u.mutation.AddSortOrder(v) + return _u +} + +// AddLocalizedInIDs adds the "localized_in" edge to the SupportedLocale entity by IDs. +func (_u *SpokenLanguageUpdate) AddLocalizedInIDs(ids ...string) *SpokenLanguageUpdate { + _u.mutation.AddLocalizedInIDs(ids...) + return _u +} + +// AddLocalizedIn adds the "localized_in" edges to the SupportedLocale entity. +func (_u *SpokenLanguageUpdate) AddLocalizedIn(v ...*SupportedLocale) *SpokenLanguageUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddLocalizedInIDs(ids...) +} + +// AddProviderProfileIDs adds the "provider_profiles" edge to the ProviderProfile entity by IDs. +func (_u *SpokenLanguageUpdate) AddProviderProfileIDs(ids ...uuid.UUID) *SpokenLanguageUpdate { + _u.mutation.AddProviderProfileIDs(ids...) + return _u +} + +// AddProviderProfiles adds the "provider_profiles" edges to the ProviderProfile entity. +func (_u *SpokenLanguageUpdate) AddProviderProfiles(v ...*ProviderProfile) *SpokenLanguageUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddProviderProfileIDs(ids...) +} + +// Mutation returns the SpokenLanguageMutation object of the builder. +func (_u *SpokenLanguageUpdate) Mutation() *SpokenLanguageMutation { + return _u.mutation +} + +// ClearLocalizedIn clears all "localized_in" edges to the SupportedLocale entity. +func (_u *SpokenLanguageUpdate) ClearLocalizedIn() *SpokenLanguageUpdate { + _u.mutation.ClearLocalizedIn() + return _u +} + +// RemoveLocalizedInIDs removes the "localized_in" edge to SupportedLocale entities by IDs. +func (_u *SpokenLanguageUpdate) RemoveLocalizedInIDs(ids ...string) *SpokenLanguageUpdate { + _u.mutation.RemoveLocalizedInIDs(ids...) + return _u +} + +// RemoveLocalizedIn removes "localized_in" edges to SupportedLocale entities. +func (_u *SpokenLanguageUpdate) RemoveLocalizedIn(v ...*SupportedLocale) *SpokenLanguageUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveLocalizedInIDs(ids...) +} + +// ClearProviderProfiles clears all "provider_profiles" edges to the ProviderProfile entity. +func (_u *SpokenLanguageUpdate) ClearProviderProfiles() *SpokenLanguageUpdate { + _u.mutation.ClearProviderProfiles() + return _u +} + +// RemoveProviderProfileIDs removes the "provider_profiles" edge to ProviderProfile entities by IDs. +func (_u *SpokenLanguageUpdate) RemoveProviderProfileIDs(ids ...uuid.UUID) *SpokenLanguageUpdate { + _u.mutation.RemoveProviderProfileIDs(ids...) + return _u +} + +// RemoveProviderProfiles removes "provider_profiles" edges to ProviderProfile entities. +func (_u *SpokenLanguageUpdate) RemoveProviderProfiles(v ...*ProviderProfile) *SpokenLanguageUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveProviderProfileIDs(ids...) +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *SpokenLanguageUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *SpokenLanguageUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *SpokenLanguageUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *SpokenLanguageUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *SpokenLanguageUpdate) check() error { + if v, ok := _u.mutation.SortOrder(); ok { + if err := spokenlanguage.SortOrderValidator(v); err != nil { + return &ValidationError{Name: "sort_order", err: fmt.Errorf(`ent: validator failed for field "SpokenLanguage.sort_order": %w`, err)} + } + } + return nil +} + +func (_u *SpokenLanguageUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(spokenlanguage.Table, spokenlanguage.Columns, sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Active(); ok { + _spec.SetField(spokenlanguage.FieldActive, field.TypeBool, value) + } + if value, ok := _u.mutation.SortOrder(); ok { + _spec.SetField(spokenlanguage.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedSortOrder(); ok { + _spec.AddField(spokenlanguage.FieldSortOrder, field.TypeInt, value) + } + if _u.mutation.LocalizedInCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: spokenlanguage.LocalizedInTable, + Columns: spokenlanguage.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedLocalizedInIDs(); len(nodes) > 0 && !_u.mutation.LocalizedInCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: spokenlanguage.LocalizedInTable, + Columns: spokenlanguage.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.LocalizedInIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: spokenlanguage.LocalizedInTable, + Columns: spokenlanguage.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _u.mutation.ProviderProfilesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: spokenlanguage.ProviderProfilesTable, + Columns: spokenlanguage.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedProviderProfilesIDs(); len(nodes) > 0 && !_u.mutation.ProviderProfilesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: spokenlanguage.ProviderProfilesTable, + Columns: spokenlanguage.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.ProviderProfilesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: spokenlanguage.ProviderProfilesTable, + Columns: spokenlanguage.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{spokenlanguage.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// SpokenLanguageUpdateOne is the builder for updating a single SpokenLanguage entity. +type SpokenLanguageUpdateOne struct { + config + fields []string + hooks []Hook + mutation *SpokenLanguageMutation +} + +// SetActive sets the "active" field. +func (_u *SpokenLanguageUpdateOne) SetActive(v bool) *SpokenLanguageUpdateOne { + _u.mutation.SetActive(v) + return _u +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_u *SpokenLanguageUpdateOne) SetNillableActive(v *bool) *SpokenLanguageUpdateOne { + if v != nil { + _u.SetActive(*v) + } + return _u +} + +// SetSortOrder sets the "sort_order" field. +func (_u *SpokenLanguageUpdateOne) SetSortOrder(v int) *SpokenLanguageUpdateOne { + _u.mutation.ResetSortOrder() + _u.mutation.SetSortOrder(v) + return _u +} + +// SetNillableSortOrder sets the "sort_order" field if the given value is not nil. +func (_u *SpokenLanguageUpdateOne) SetNillableSortOrder(v *int) *SpokenLanguageUpdateOne { + if v != nil { + _u.SetSortOrder(*v) + } + return _u +} + +// AddSortOrder adds value to the "sort_order" field. +func (_u *SpokenLanguageUpdateOne) AddSortOrder(v int) *SpokenLanguageUpdateOne { + _u.mutation.AddSortOrder(v) + return _u +} + +// AddLocalizedInIDs adds the "localized_in" edge to the SupportedLocale entity by IDs. +func (_u *SpokenLanguageUpdateOne) AddLocalizedInIDs(ids ...string) *SpokenLanguageUpdateOne { + _u.mutation.AddLocalizedInIDs(ids...) + return _u +} + +// AddLocalizedIn adds the "localized_in" edges to the SupportedLocale entity. +func (_u *SpokenLanguageUpdateOne) AddLocalizedIn(v ...*SupportedLocale) *SpokenLanguageUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddLocalizedInIDs(ids...) +} + +// AddProviderProfileIDs adds the "provider_profiles" edge to the ProviderProfile entity by IDs. +func (_u *SpokenLanguageUpdateOne) AddProviderProfileIDs(ids ...uuid.UUID) *SpokenLanguageUpdateOne { + _u.mutation.AddProviderProfileIDs(ids...) + return _u +} + +// AddProviderProfiles adds the "provider_profiles" edges to the ProviderProfile entity. +func (_u *SpokenLanguageUpdateOne) AddProviderProfiles(v ...*ProviderProfile) *SpokenLanguageUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddProviderProfileIDs(ids...) +} + +// Mutation returns the SpokenLanguageMutation object of the builder. +func (_u *SpokenLanguageUpdateOne) Mutation() *SpokenLanguageMutation { + return _u.mutation +} + +// ClearLocalizedIn clears all "localized_in" edges to the SupportedLocale entity. +func (_u *SpokenLanguageUpdateOne) ClearLocalizedIn() *SpokenLanguageUpdateOne { + _u.mutation.ClearLocalizedIn() + return _u +} + +// RemoveLocalizedInIDs removes the "localized_in" edge to SupportedLocale entities by IDs. +func (_u *SpokenLanguageUpdateOne) RemoveLocalizedInIDs(ids ...string) *SpokenLanguageUpdateOne { + _u.mutation.RemoveLocalizedInIDs(ids...) + return _u +} + +// RemoveLocalizedIn removes "localized_in" edges to SupportedLocale entities. +func (_u *SpokenLanguageUpdateOne) RemoveLocalizedIn(v ...*SupportedLocale) *SpokenLanguageUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveLocalizedInIDs(ids...) +} + +// ClearProviderProfiles clears all "provider_profiles" edges to the ProviderProfile entity. +func (_u *SpokenLanguageUpdateOne) ClearProviderProfiles() *SpokenLanguageUpdateOne { + _u.mutation.ClearProviderProfiles() + return _u +} + +// RemoveProviderProfileIDs removes the "provider_profiles" edge to ProviderProfile entities by IDs. +func (_u *SpokenLanguageUpdateOne) RemoveProviderProfileIDs(ids ...uuid.UUID) *SpokenLanguageUpdateOne { + _u.mutation.RemoveProviderProfileIDs(ids...) + return _u +} + +// RemoveProviderProfiles removes "provider_profiles" edges to ProviderProfile entities. +func (_u *SpokenLanguageUpdateOne) RemoveProviderProfiles(v ...*ProviderProfile) *SpokenLanguageUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveProviderProfileIDs(ids...) +} + +// Where appends a list predicates to the SpokenLanguageUpdate builder. +func (_u *SpokenLanguageUpdateOne) Where(ps ...predicate.SpokenLanguage) *SpokenLanguageUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *SpokenLanguageUpdateOne) Select(field string, fields ...string) *SpokenLanguageUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated SpokenLanguage entity. +func (_u *SpokenLanguageUpdateOne) Save(ctx context.Context) (*SpokenLanguage, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *SpokenLanguageUpdateOne) SaveX(ctx context.Context) *SpokenLanguage { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *SpokenLanguageUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *SpokenLanguageUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *SpokenLanguageUpdateOne) check() error { + if v, ok := _u.mutation.SortOrder(); ok { + if err := spokenlanguage.SortOrderValidator(v); err != nil { + return &ValidationError{Name: "sort_order", err: fmt.Errorf(`ent: validator failed for field "SpokenLanguage.sort_order": %w`, err)} + } + } + return nil +} + +func (_u *SpokenLanguageUpdateOne) sqlSave(ctx context.Context) (_node *SpokenLanguage, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(spokenlanguage.Table, spokenlanguage.Columns, sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "SpokenLanguage.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, spokenlanguage.FieldID) + for _, f := range fields { + if !spokenlanguage.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != spokenlanguage.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Active(); ok { + _spec.SetField(spokenlanguage.FieldActive, field.TypeBool, value) + } + if value, ok := _u.mutation.SortOrder(); ok { + _spec.SetField(spokenlanguage.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedSortOrder(); ok { + _spec.AddField(spokenlanguage.FieldSortOrder, field.TypeInt, value) + } + if _u.mutation.LocalizedInCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: spokenlanguage.LocalizedInTable, + Columns: spokenlanguage.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedLocalizedInIDs(); len(nodes) > 0 && !_u.mutation.LocalizedInCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: spokenlanguage.LocalizedInTable, + Columns: spokenlanguage.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.LocalizedInIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: spokenlanguage.LocalizedInTable, + Columns: spokenlanguage.LocalizedInPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _u.mutation.ProviderProfilesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: spokenlanguage.ProviderProfilesTable, + Columns: spokenlanguage.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedProviderProfilesIDs(); len(nodes) > 0 && !_u.mutation.ProviderProfilesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: spokenlanguage.ProviderProfilesTable, + Columns: spokenlanguage.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.ProviderProfilesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: spokenlanguage.ProviderProfilesTable, + Columns: spokenlanguage.ProviderProfilesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(providerprofile.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &SpokenLanguage{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{spokenlanguage.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/ent/spokenlanguagetranslation.go b/backend/ent/spokenlanguagetranslation.go new file mode 100644 index 0000000..33b157c --- /dev/null +++ b/backend/ent/spokenlanguagetranslation.go @@ -0,0 +1,162 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguagetranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" +) + +// SpokenLanguageTranslation is the model entity for the SpokenLanguageTranslation schema. +type SpokenLanguageTranslation struct { + config `json:"-"` + // LanguageCode holds the value of the "language_code" field. + LanguageCode string `json:"language_code,omitempty"` + // Locale holds the value of the "locale" field. + Locale string `json:"locale,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the SpokenLanguageTranslationQuery when eager-loading is set. + Edges SpokenLanguageTranslationEdges `json:"edges"` + selectValues sql.SelectValues +} + +// SpokenLanguageTranslationEdges holds the relations/edges for other nodes in the graph. +type SpokenLanguageTranslationEdges struct { + // Language holds the value of the language edge. + Language *SpokenLanguage `json:"language,omitempty"` + // LocaleRecord holds the value of the locale_record edge. + LocaleRecord *SupportedLocale `json:"locale_record,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [2]bool +} + +// LanguageOrErr returns the Language value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e SpokenLanguageTranslationEdges) LanguageOrErr() (*SpokenLanguage, error) { + if e.Language != nil { + return e.Language, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: spokenlanguage.Label} + } + return nil, &NotLoadedError{edge: "language"} +} + +// LocaleRecordOrErr returns the LocaleRecord value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e SpokenLanguageTranslationEdges) LocaleRecordOrErr() (*SupportedLocale, error) { + if e.LocaleRecord != nil { + return e.LocaleRecord, nil + } else if e.loadedTypes[1] { + return nil, &NotFoundError{label: supportedlocale.Label} + } + return nil, &NotLoadedError{edge: "locale_record"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*SpokenLanguageTranslation) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case spokenlanguagetranslation.FieldLanguageCode, spokenlanguagetranslation.FieldLocale, spokenlanguagetranslation.FieldName: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the SpokenLanguageTranslation fields. +func (_m *SpokenLanguageTranslation) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case spokenlanguagetranslation.FieldLanguageCode: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field language_code", values[i]) + } else if value.Valid { + _m.LanguageCode = value.String + } + case spokenlanguagetranslation.FieldLocale: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field locale", values[i]) + } else if value.Valid { + _m.Locale = value.String + } + case spokenlanguagetranslation.FieldName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field name", values[i]) + } else if value.Valid { + _m.Name = value.String + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the SpokenLanguageTranslation. +// This includes values selected through modifiers, order, etc. +func (_m *SpokenLanguageTranslation) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QueryLanguage queries the "language" edge of the SpokenLanguageTranslation entity. +func (_m *SpokenLanguageTranslation) QueryLanguage() *SpokenLanguageQuery { + return NewSpokenLanguageTranslationClient(_m.config).QueryLanguage(_m) +} + +// QueryLocaleRecord queries the "locale_record" edge of the SpokenLanguageTranslation entity. +func (_m *SpokenLanguageTranslation) QueryLocaleRecord() *SupportedLocaleQuery { + return NewSpokenLanguageTranslationClient(_m.config).QueryLocaleRecord(_m) +} + +// Update returns a builder for updating this SpokenLanguageTranslation. +// Note that you need to call SpokenLanguageTranslation.Unwrap() before calling this method if this SpokenLanguageTranslation +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *SpokenLanguageTranslation) Update() *SpokenLanguageTranslationUpdateOne { + return NewSpokenLanguageTranslationClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the SpokenLanguageTranslation entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *SpokenLanguageTranslation) Unwrap() *SpokenLanguageTranslation { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: SpokenLanguageTranslation is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *SpokenLanguageTranslation) String() string { + var builder strings.Builder + builder.WriteString("SpokenLanguageTranslation(") + builder.WriteString("language_code=") + builder.WriteString(_m.LanguageCode) + builder.WriteString(", ") + builder.WriteString("locale=") + builder.WriteString(_m.Locale) + builder.WriteString(", ") + builder.WriteString("name=") + builder.WriteString(_m.Name) + builder.WriteByte(')') + return builder.String() +} + +// SpokenLanguageTranslations is a parsable slice of SpokenLanguageTranslation. +type SpokenLanguageTranslations []*SpokenLanguageTranslation diff --git a/backend/ent/spokenlanguagetranslation/spokenlanguagetranslation.go b/backend/ent/spokenlanguagetranslation/spokenlanguagetranslation.go new file mode 100644 index 0000000..65adebc --- /dev/null +++ b/backend/ent/spokenlanguagetranslation/spokenlanguagetranslation.go @@ -0,0 +1,115 @@ +// Code generated by ent, DO NOT EDIT. + +package spokenlanguagetranslation + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the spokenlanguagetranslation type in the database. + Label = "spoken_language_translation" + // FieldLanguageCode holds the string denoting the language_code field in the database. + FieldLanguageCode = "language_code" + // FieldLocale holds the string denoting the locale field in the database. + FieldLocale = "locale" + // FieldName holds the string denoting the name field in the database. + FieldName = "name" + // EdgeLanguage holds the string denoting the language edge name in mutations. + EdgeLanguage = "language" + // EdgeLocaleRecord holds the string denoting the locale_record edge name in mutations. + EdgeLocaleRecord = "locale_record" + // SpokenLanguageFieldID holds the string denoting the ID field of the SpokenLanguage. + SpokenLanguageFieldID = "id" + // SupportedLocaleFieldID holds the string denoting the ID field of the SupportedLocale. + SupportedLocaleFieldID = "id" + // Table holds the table name of the spokenlanguagetranslation in the database. + Table = "spoken_language_translations" + // LanguageTable is the table that holds the language relation/edge. + LanguageTable = "spoken_language_translations" + // LanguageInverseTable is the table name for the SpokenLanguage entity. + // It exists in this package in order to avoid circular dependency with the "spokenlanguage" package. + LanguageInverseTable = "spoken_languages" + // LanguageColumn is the table column denoting the language relation/edge. + LanguageColumn = "language_code" + // LocaleRecordTable is the table that holds the locale_record relation/edge. + LocaleRecordTable = "spoken_language_translations" + // LocaleRecordInverseTable is the table name for the SupportedLocale entity. + // It exists in this package in order to avoid circular dependency with the "supportedlocale" package. + LocaleRecordInverseTable = "supported_locales" + // LocaleRecordColumn is the table column denoting the locale_record relation/edge. + LocaleRecordColumn = "locale" +) + +// Columns holds all SQL columns for spokenlanguagetranslation fields. +var Columns = []string{ + FieldLanguageCode, + FieldLocale, + FieldName, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // LanguageCodeValidator is a validator for the "language_code" field. It is called by the builders before save. + LanguageCodeValidator func(string) error + // LocaleValidator is a validator for the "locale" field. It is called by the builders before save. + LocaleValidator func(string) error + // NameValidator is a validator for the "name" field. It is called by the builders before save. + NameValidator func(string) error +) + +// OrderOption defines the ordering options for the SpokenLanguageTranslation queries. +type OrderOption func(*sql.Selector) + +// ByLanguageCode orders the results by the language_code field. +func ByLanguageCode(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLanguageCode, opts...).ToFunc() +} + +// ByLocale orders the results by the locale field. +func ByLocale(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLocale, opts...).ToFunc() +} + +// ByName orders the results by the name field. +func ByName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldName, opts...).ToFunc() +} + +// ByLanguageField orders the results by language field. +func ByLanguageField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newLanguageStep(), sql.OrderByField(field, opts...)) + } +} + +// ByLocaleRecordField orders the results by locale_record field. +func ByLocaleRecordField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newLocaleRecordStep(), sql.OrderByField(field, opts...)) + } +} +func newLanguageStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, LanguageColumn), + sqlgraph.To(LanguageInverseTable, SpokenLanguageFieldID), + sqlgraph.Edge(sqlgraph.M2O, false, LanguageTable, LanguageColumn), + ) +} +func newLocaleRecordStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, LocaleRecordColumn), + sqlgraph.To(LocaleRecordInverseTable, SupportedLocaleFieldID), + sqlgraph.Edge(sqlgraph.M2O, false, LocaleRecordTable, LocaleRecordColumn), + ) +} diff --git a/backend/ent/spokenlanguagetranslation/where.go b/backend/ent/spokenlanguagetranslation/where.go new file mode 100644 index 0000000..769ee1a --- /dev/null +++ b/backend/ent/spokenlanguagetranslation/where.go @@ -0,0 +1,280 @@ +// Code generated by ent, DO NOT EDIT. + +package spokenlanguagetranslation + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" +) + +// LanguageCode applies equality check predicate on the "language_code" field. It's identical to LanguageCodeEQ. +func LanguageCode(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldEQ(FieldLanguageCode, v)) +} + +// Locale applies equality check predicate on the "locale" field. It's identical to LocaleEQ. +func Locale(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldEQ(FieldLocale, v)) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldEQ(FieldName, v)) +} + +// LanguageCodeEQ applies the EQ predicate on the "language_code" field. +func LanguageCodeEQ(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldEQ(FieldLanguageCode, v)) +} + +// LanguageCodeNEQ applies the NEQ predicate on the "language_code" field. +func LanguageCodeNEQ(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldNEQ(FieldLanguageCode, v)) +} + +// LanguageCodeIn applies the In predicate on the "language_code" field. +func LanguageCodeIn(vs ...string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldIn(FieldLanguageCode, vs...)) +} + +// LanguageCodeNotIn applies the NotIn predicate on the "language_code" field. +func LanguageCodeNotIn(vs ...string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldNotIn(FieldLanguageCode, vs...)) +} + +// LanguageCodeGT applies the GT predicate on the "language_code" field. +func LanguageCodeGT(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldGT(FieldLanguageCode, v)) +} + +// LanguageCodeGTE applies the GTE predicate on the "language_code" field. +func LanguageCodeGTE(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldGTE(FieldLanguageCode, v)) +} + +// LanguageCodeLT applies the LT predicate on the "language_code" field. +func LanguageCodeLT(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldLT(FieldLanguageCode, v)) +} + +// LanguageCodeLTE applies the LTE predicate on the "language_code" field. +func LanguageCodeLTE(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldLTE(FieldLanguageCode, v)) +} + +// LanguageCodeContains applies the Contains predicate on the "language_code" field. +func LanguageCodeContains(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldContains(FieldLanguageCode, v)) +} + +// LanguageCodeHasPrefix applies the HasPrefix predicate on the "language_code" field. +func LanguageCodeHasPrefix(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldHasPrefix(FieldLanguageCode, v)) +} + +// LanguageCodeHasSuffix applies the HasSuffix predicate on the "language_code" field. +func LanguageCodeHasSuffix(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldHasSuffix(FieldLanguageCode, v)) +} + +// LanguageCodeEqualFold applies the EqualFold predicate on the "language_code" field. +func LanguageCodeEqualFold(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldEqualFold(FieldLanguageCode, v)) +} + +// LanguageCodeContainsFold applies the ContainsFold predicate on the "language_code" field. +func LanguageCodeContainsFold(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldContainsFold(FieldLanguageCode, v)) +} + +// LocaleEQ applies the EQ predicate on the "locale" field. +func LocaleEQ(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldEQ(FieldLocale, v)) +} + +// LocaleNEQ applies the NEQ predicate on the "locale" field. +func LocaleNEQ(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldNEQ(FieldLocale, v)) +} + +// LocaleIn applies the In predicate on the "locale" field. +func LocaleIn(vs ...string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldIn(FieldLocale, vs...)) +} + +// LocaleNotIn applies the NotIn predicate on the "locale" field. +func LocaleNotIn(vs ...string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldNotIn(FieldLocale, vs...)) +} + +// LocaleGT applies the GT predicate on the "locale" field. +func LocaleGT(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldGT(FieldLocale, v)) +} + +// LocaleGTE applies the GTE predicate on the "locale" field. +func LocaleGTE(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldGTE(FieldLocale, v)) +} + +// LocaleLT applies the LT predicate on the "locale" field. +func LocaleLT(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldLT(FieldLocale, v)) +} + +// LocaleLTE applies the LTE predicate on the "locale" field. +func LocaleLTE(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldLTE(FieldLocale, v)) +} + +// LocaleContains applies the Contains predicate on the "locale" field. +func LocaleContains(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldContains(FieldLocale, v)) +} + +// LocaleHasPrefix applies the HasPrefix predicate on the "locale" field. +func LocaleHasPrefix(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldHasPrefix(FieldLocale, v)) +} + +// LocaleHasSuffix applies the HasSuffix predicate on the "locale" field. +func LocaleHasSuffix(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldHasSuffix(FieldLocale, v)) +} + +// LocaleEqualFold applies the EqualFold predicate on the "locale" field. +func LocaleEqualFold(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldEqualFold(FieldLocale, v)) +} + +// LocaleContainsFold applies the ContainsFold predicate on the "locale" field. +func LocaleContainsFold(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldContainsFold(FieldLocale, v)) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldEQ(FieldName, v)) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldNEQ(FieldName, v)) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldIn(FieldName, vs...)) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldNotIn(FieldName, vs...)) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldGT(FieldName, v)) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldGTE(FieldName, v)) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldLT(FieldName, v)) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldLTE(FieldName, v)) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldContains(FieldName, v)) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldHasPrefix(FieldName, v)) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldHasSuffix(FieldName, v)) +} + +// NameEqualFold applies the EqualFold predicate on the "name" field. +func NameEqualFold(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldEqualFold(FieldName, v)) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.FieldContainsFold(FieldName, v)) +} + +// HasLanguage applies the HasEdge predicate on the "language" edge. +func HasLanguage() predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, LanguageColumn), + sqlgraph.Edge(sqlgraph.M2O, false, LanguageTable, LanguageColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasLanguageWith applies the HasEdge predicate on the "language" edge with a given conditions (other predicates). +func HasLanguageWith(preds ...predicate.SpokenLanguage) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(func(s *sql.Selector) { + step := newLanguageStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasLocaleRecord applies the HasEdge predicate on the "locale_record" edge. +func HasLocaleRecord() predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, LocaleRecordColumn), + sqlgraph.Edge(sqlgraph.M2O, false, LocaleRecordTable, LocaleRecordColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasLocaleRecordWith applies the HasEdge predicate on the "locale_record" edge with a given conditions (other predicates). +func HasLocaleRecordWith(preds ...predicate.SupportedLocale) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(func(s *sql.Selector) { + step := newLocaleRecordStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.SpokenLanguageTranslation) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.SpokenLanguageTranslation) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.SpokenLanguageTranslation) predicate.SpokenLanguageTranslation { + return predicate.SpokenLanguageTranslation(sql.NotPredicates(p)) +} diff --git a/backend/ent/spokenlanguagetranslation_create.go b/backend/ent/spokenlanguagetranslation_create.go new file mode 100644 index 0000000..c7ead63 --- /dev/null +++ b/backend/ent/spokenlanguagetranslation_create.go @@ -0,0 +1,271 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguagetranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" +) + +// SpokenLanguageTranslationCreate is the builder for creating a SpokenLanguageTranslation entity. +type SpokenLanguageTranslationCreate struct { + config + mutation *SpokenLanguageTranslationMutation + hooks []Hook +} + +// SetLanguageCode sets the "language_code" field. +func (_c *SpokenLanguageTranslationCreate) SetLanguageCode(v string) *SpokenLanguageTranslationCreate { + _c.mutation.SetLanguageCode(v) + return _c +} + +// SetLocale sets the "locale" field. +func (_c *SpokenLanguageTranslationCreate) SetLocale(v string) *SpokenLanguageTranslationCreate { + _c.mutation.SetLocale(v) + return _c +} + +// SetName sets the "name" field. +func (_c *SpokenLanguageTranslationCreate) SetName(v string) *SpokenLanguageTranslationCreate { + _c.mutation.SetName(v) + return _c +} + +// SetLanguageID sets the "language" edge to the SpokenLanguage entity by ID. +func (_c *SpokenLanguageTranslationCreate) SetLanguageID(id string) *SpokenLanguageTranslationCreate { + _c.mutation.SetLanguageID(id) + return _c +} + +// SetLanguage sets the "language" edge to the SpokenLanguage entity. +func (_c *SpokenLanguageTranslationCreate) SetLanguage(v *SpokenLanguage) *SpokenLanguageTranslationCreate { + return _c.SetLanguageID(v.ID) +} + +// SetLocaleRecordID sets the "locale_record" edge to the SupportedLocale entity by ID. +func (_c *SpokenLanguageTranslationCreate) SetLocaleRecordID(id string) *SpokenLanguageTranslationCreate { + _c.mutation.SetLocaleRecordID(id) + return _c +} + +// SetLocaleRecord sets the "locale_record" edge to the SupportedLocale entity. +func (_c *SpokenLanguageTranslationCreate) SetLocaleRecord(v *SupportedLocale) *SpokenLanguageTranslationCreate { + return _c.SetLocaleRecordID(v.ID) +} + +// Mutation returns the SpokenLanguageTranslationMutation object of the builder. +func (_c *SpokenLanguageTranslationCreate) Mutation() *SpokenLanguageTranslationMutation { + return _c.mutation +} + +// Save creates the SpokenLanguageTranslation in the database. +func (_c *SpokenLanguageTranslationCreate) Save(ctx context.Context) (*SpokenLanguageTranslation, error) { + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *SpokenLanguageTranslationCreate) SaveX(ctx context.Context) *SpokenLanguageTranslation { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *SpokenLanguageTranslationCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *SpokenLanguageTranslationCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *SpokenLanguageTranslationCreate) check() error { + if _, ok := _c.mutation.LanguageCode(); !ok { + return &ValidationError{Name: "language_code", err: errors.New(`ent: missing required field "SpokenLanguageTranslation.language_code"`)} + } + if v, ok := _c.mutation.LanguageCode(); ok { + if err := spokenlanguagetranslation.LanguageCodeValidator(v); err != nil { + return &ValidationError{Name: "language_code", err: fmt.Errorf(`ent: validator failed for field "SpokenLanguageTranslation.language_code": %w`, err)} + } + } + if _, ok := _c.mutation.Locale(); !ok { + return &ValidationError{Name: "locale", err: errors.New(`ent: missing required field "SpokenLanguageTranslation.locale"`)} + } + if v, ok := _c.mutation.Locale(); ok { + if err := spokenlanguagetranslation.LocaleValidator(v); err != nil { + return &ValidationError{Name: "locale", err: fmt.Errorf(`ent: validator failed for field "SpokenLanguageTranslation.locale": %w`, err)} + } + } + if _, ok := _c.mutation.Name(); !ok { + return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "SpokenLanguageTranslation.name"`)} + } + if v, ok := _c.mutation.Name(); ok { + if err := spokenlanguagetranslation.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "SpokenLanguageTranslation.name": %w`, err)} + } + } + if len(_c.mutation.LanguageIDs()) == 0 { + return &ValidationError{Name: "language", err: errors.New(`ent: missing required edge "SpokenLanguageTranslation.language"`)} + } + if len(_c.mutation.LocaleRecordIDs()) == 0 { + return &ValidationError{Name: "locale_record", err: errors.New(`ent: missing required edge "SpokenLanguageTranslation.locale_record"`)} + } + return nil +} + +func (_c *SpokenLanguageTranslationCreate) sqlSave(ctx context.Context) (*SpokenLanguageTranslation, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + return _node, nil +} + +func (_c *SpokenLanguageTranslationCreate) createSpec() (*SpokenLanguageTranslation, *sqlgraph.CreateSpec) { + var ( + _node = &SpokenLanguageTranslation{config: _c.config} + _spec = sqlgraph.NewCreateSpec(spokenlanguagetranslation.Table, nil) + ) + if value, ok := _c.mutation.Name(); ok { + _spec.SetField(spokenlanguagetranslation.FieldName, field.TypeString, value) + _node.Name = value + } + if nodes := _c.mutation.LanguageIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: spokenlanguagetranslation.LanguageTable, + Columns: []string{spokenlanguagetranslation.LanguageColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.LanguageCode = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.LocaleRecordIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: false, + Table: spokenlanguagetranslation.LocaleRecordTable, + Columns: []string{spokenlanguagetranslation.LocaleRecordColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.Locale = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// SpokenLanguageTranslationCreateBulk is the builder for creating many SpokenLanguageTranslation entities in bulk. +type SpokenLanguageTranslationCreateBulk struct { + config + err error + builders []*SpokenLanguageTranslationCreate +} + +// Save creates the SpokenLanguageTranslation entities in the database. +func (_c *SpokenLanguageTranslationCreateBulk) Save(ctx context.Context) ([]*SpokenLanguageTranslation, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*SpokenLanguageTranslation, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*SpokenLanguageTranslationMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *SpokenLanguageTranslationCreateBulk) SaveX(ctx context.Context) []*SpokenLanguageTranslation { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *SpokenLanguageTranslationCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *SpokenLanguageTranslationCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/spokenlanguagetranslation_delete.go b/backend/ent/spokenlanguagetranslation_delete.go new file mode 100644 index 0000000..2e7ce4d --- /dev/null +++ b/backend/ent/spokenlanguagetranslation_delete.go @@ -0,0 +1,87 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguagetranslation" +) + +// SpokenLanguageTranslationDelete is the builder for deleting a SpokenLanguageTranslation entity. +type SpokenLanguageTranslationDelete struct { + config + hooks []Hook + mutation *SpokenLanguageTranslationMutation +} + +// Where appends a list predicates to the SpokenLanguageTranslationDelete builder. +func (_d *SpokenLanguageTranslationDelete) Where(ps ...predicate.SpokenLanguageTranslation) *SpokenLanguageTranslationDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *SpokenLanguageTranslationDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *SpokenLanguageTranslationDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *SpokenLanguageTranslationDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(spokenlanguagetranslation.Table, nil) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// SpokenLanguageTranslationDeleteOne is the builder for deleting a single SpokenLanguageTranslation entity. +type SpokenLanguageTranslationDeleteOne struct { + _d *SpokenLanguageTranslationDelete +} + +// Where appends a list predicates to the SpokenLanguageTranslationDelete builder. +func (_d *SpokenLanguageTranslationDeleteOne) Where(ps ...predicate.SpokenLanguageTranslation) *SpokenLanguageTranslationDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *SpokenLanguageTranslationDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{spokenlanguagetranslation.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *SpokenLanguageTranslationDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/spokenlanguagetranslation_query.go b/backend/ent/spokenlanguagetranslation_query.go new file mode 100644 index 0000000..2b433fe --- /dev/null +++ b/backend/ent/spokenlanguagetranslation_query.go @@ -0,0 +1,603 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguagetranslation" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" +) + +// SpokenLanguageTranslationQuery is the builder for querying SpokenLanguageTranslation entities. +type SpokenLanguageTranslationQuery struct { + config + ctx *QueryContext + order []spokenlanguagetranslation.OrderOption + inters []Interceptor + predicates []predicate.SpokenLanguageTranslation + withLanguage *SpokenLanguageQuery + withLocaleRecord *SupportedLocaleQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the SpokenLanguageTranslationQuery builder. +func (_q *SpokenLanguageTranslationQuery) Where(ps ...predicate.SpokenLanguageTranslation) *SpokenLanguageTranslationQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *SpokenLanguageTranslationQuery) Limit(limit int) *SpokenLanguageTranslationQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *SpokenLanguageTranslationQuery) Offset(offset int) *SpokenLanguageTranslationQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *SpokenLanguageTranslationQuery) Unique(unique bool) *SpokenLanguageTranslationQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *SpokenLanguageTranslationQuery) Order(o ...spokenlanguagetranslation.OrderOption) *SpokenLanguageTranslationQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QueryLanguage chains the current query on the "language" edge. +func (_q *SpokenLanguageTranslationQuery) QueryLanguage() *SpokenLanguageQuery { + query := (&SpokenLanguageClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(spokenlanguagetranslation.Table, spokenlanguagetranslation.LanguageColumn, selector), + sqlgraph.To(spokenlanguage.Table, spokenlanguage.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, spokenlanguagetranslation.LanguageTable, spokenlanguagetranslation.LanguageColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryLocaleRecord chains the current query on the "locale_record" edge. +func (_q *SpokenLanguageTranslationQuery) QueryLocaleRecord() *SupportedLocaleQuery { + query := (&SupportedLocaleClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(spokenlanguagetranslation.Table, spokenlanguagetranslation.LocaleRecordColumn, selector), + sqlgraph.To(supportedlocale.Table, supportedlocale.FieldID), + sqlgraph.Edge(sqlgraph.M2O, false, spokenlanguagetranslation.LocaleRecordTable, spokenlanguagetranslation.LocaleRecordColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first SpokenLanguageTranslation entity from the query. +// Returns a *NotFoundError when no SpokenLanguageTranslation was found. +func (_q *SpokenLanguageTranslationQuery) First(ctx context.Context) (*SpokenLanguageTranslation, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{spokenlanguagetranslation.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *SpokenLanguageTranslationQuery) FirstX(ctx context.Context) *SpokenLanguageTranslation { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// Only returns a single SpokenLanguageTranslation entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one SpokenLanguageTranslation entity is found. +// Returns a *NotFoundError when no SpokenLanguageTranslation entities are found. +func (_q *SpokenLanguageTranslationQuery) Only(ctx context.Context) (*SpokenLanguageTranslation, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{spokenlanguagetranslation.Label} + default: + return nil, &NotSingularError{spokenlanguagetranslation.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *SpokenLanguageTranslationQuery) OnlyX(ctx context.Context) *SpokenLanguageTranslation { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// All executes the query and returns a list of SpokenLanguageTranslations. +func (_q *SpokenLanguageTranslationQuery) All(ctx context.Context) ([]*SpokenLanguageTranslation, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*SpokenLanguageTranslation, *SpokenLanguageTranslationQuery]() + return withInterceptors[[]*SpokenLanguageTranslation](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *SpokenLanguageTranslationQuery) AllX(ctx context.Context) []*SpokenLanguageTranslation { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// Count returns the count of the given query. +func (_q *SpokenLanguageTranslationQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*SpokenLanguageTranslationQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *SpokenLanguageTranslationQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *SpokenLanguageTranslationQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.First(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *SpokenLanguageTranslationQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the SpokenLanguageTranslationQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *SpokenLanguageTranslationQuery) Clone() *SpokenLanguageTranslationQuery { + if _q == nil { + return nil + } + return &SpokenLanguageTranslationQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]spokenlanguagetranslation.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.SpokenLanguageTranslation{}, _q.predicates...), + withLanguage: _q.withLanguage.Clone(), + withLocaleRecord: _q.withLocaleRecord.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// WithLanguage tells the query-builder to eager-load the nodes that are connected to +// the "language" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *SpokenLanguageTranslationQuery) WithLanguage(opts ...func(*SpokenLanguageQuery)) *SpokenLanguageTranslationQuery { + query := (&SpokenLanguageClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withLanguage = query + return _q +} + +// WithLocaleRecord tells the query-builder to eager-load the nodes that are connected to +// the "locale_record" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *SpokenLanguageTranslationQuery) WithLocaleRecord(opts ...func(*SupportedLocaleQuery)) *SpokenLanguageTranslationQuery { + query := (&SupportedLocaleClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withLocaleRecord = query + return _q +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// LanguageCode string `json:"language_code,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.SpokenLanguageTranslation.Query(). +// GroupBy(spokenlanguagetranslation.FieldLanguageCode). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *SpokenLanguageTranslationQuery) GroupBy(field string, fields ...string) *SpokenLanguageTranslationGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &SpokenLanguageTranslationGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = spokenlanguagetranslation.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// LanguageCode string `json:"language_code,omitempty"` +// } +// +// client.SpokenLanguageTranslation.Query(). +// Select(spokenlanguagetranslation.FieldLanguageCode). +// Scan(ctx, &v) +func (_q *SpokenLanguageTranslationQuery) Select(fields ...string) *SpokenLanguageTranslationSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &SpokenLanguageTranslationSelect{SpokenLanguageTranslationQuery: _q} + sbuild.label = spokenlanguagetranslation.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a SpokenLanguageTranslationSelect configured with the given aggregations. +func (_q *SpokenLanguageTranslationQuery) Aggregate(fns ...AggregateFunc) *SpokenLanguageTranslationSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *SpokenLanguageTranslationQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !spokenlanguagetranslation.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *SpokenLanguageTranslationQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*SpokenLanguageTranslation, error) { + var ( + nodes = []*SpokenLanguageTranslation{} + _spec = _q.querySpec() + loadedTypes = [2]bool{ + _q.withLanguage != nil, + _q.withLocaleRecord != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*SpokenLanguageTranslation).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &SpokenLanguageTranslation{config: _q.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := _q.withLanguage; query != nil { + if err := _q.loadLanguage(ctx, query, nodes, nil, + func(n *SpokenLanguageTranslation, e *SpokenLanguage) { n.Edges.Language = e }); err != nil { + return nil, err + } + } + if query := _q.withLocaleRecord; query != nil { + if err := _q.loadLocaleRecord(ctx, query, nodes, nil, + func(n *SpokenLanguageTranslation, e *SupportedLocale) { n.Edges.LocaleRecord = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *SpokenLanguageTranslationQuery) loadLanguage(ctx context.Context, query *SpokenLanguageQuery, nodes []*SpokenLanguageTranslation, init func(*SpokenLanguageTranslation), assign func(*SpokenLanguageTranslation, *SpokenLanguage)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*SpokenLanguageTranslation) + for i := range nodes { + fk := nodes[i].LanguageCode + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(spokenlanguage.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "language_code" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (_q *SpokenLanguageTranslationQuery) loadLocaleRecord(ctx context.Context, query *SupportedLocaleQuery, nodes []*SpokenLanguageTranslation, init func(*SpokenLanguageTranslation), assign func(*SpokenLanguageTranslation, *SupportedLocale)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*SpokenLanguageTranslation) + for i := range nodes { + fk := nodes[i].Locale + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(supportedlocale.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "locale" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (_q *SpokenLanguageTranslationQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Unique = false + _spec.Node.Columns = nil + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *SpokenLanguageTranslationQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(spokenlanguagetranslation.Table, spokenlanguagetranslation.Columns, nil) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + for i := range fields { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + if _q.withLanguage != nil { + _spec.Node.AddColumnOnce(spokenlanguagetranslation.FieldLanguageCode) + } + if _q.withLocaleRecord != nil { + _spec.Node.AddColumnOnce(spokenlanguagetranslation.FieldLocale) + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *SpokenLanguageTranslationQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(spokenlanguagetranslation.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = spokenlanguagetranslation.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// SpokenLanguageTranslationGroupBy is the group-by builder for SpokenLanguageTranslation entities. +type SpokenLanguageTranslationGroupBy struct { + selector + build *SpokenLanguageTranslationQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *SpokenLanguageTranslationGroupBy) Aggregate(fns ...AggregateFunc) *SpokenLanguageTranslationGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *SpokenLanguageTranslationGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*SpokenLanguageTranslationQuery, *SpokenLanguageTranslationGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *SpokenLanguageTranslationGroupBy) sqlScan(ctx context.Context, root *SpokenLanguageTranslationQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// SpokenLanguageTranslationSelect is the builder for selecting fields of SpokenLanguageTranslation entities. +type SpokenLanguageTranslationSelect struct { + *SpokenLanguageTranslationQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *SpokenLanguageTranslationSelect) Aggregate(fns ...AggregateFunc) *SpokenLanguageTranslationSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *SpokenLanguageTranslationSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*SpokenLanguageTranslationQuery, *SpokenLanguageTranslationSelect](ctx, _s.SpokenLanguageTranslationQuery, _s, _s.inters, v) +} + +func (_s *SpokenLanguageTranslationSelect) sqlScan(ctx context.Context, root *SpokenLanguageTranslationQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/spokenlanguagetranslation_update.go b/backend/ent/spokenlanguagetranslation_update.go new file mode 100644 index 0000000..57d02d0 --- /dev/null +++ b/backend/ent/spokenlanguagetranslation_update.go @@ -0,0 +1,249 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguagetranslation" +) + +// SpokenLanguageTranslationUpdate is the builder for updating SpokenLanguageTranslation entities. +type SpokenLanguageTranslationUpdate struct { + config + hooks []Hook + mutation *SpokenLanguageTranslationMutation +} + +// Where appends a list predicates to the SpokenLanguageTranslationUpdate builder. +func (_u *SpokenLanguageTranslationUpdate) Where(ps ...predicate.SpokenLanguageTranslation) *SpokenLanguageTranslationUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetName sets the "name" field. +func (_u *SpokenLanguageTranslationUpdate) SetName(v string) *SpokenLanguageTranslationUpdate { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *SpokenLanguageTranslationUpdate) SetNillableName(v *string) *SpokenLanguageTranslationUpdate { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// Mutation returns the SpokenLanguageTranslationMutation object of the builder. +func (_u *SpokenLanguageTranslationUpdate) Mutation() *SpokenLanguageTranslationMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *SpokenLanguageTranslationUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *SpokenLanguageTranslationUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *SpokenLanguageTranslationUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *SpokenLanguageTranslationUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *SpokenLanguageTranslationUpdate) check() error { + if v, ok := _u.mutation.Name(); ok { + if err := spokenlanguagetranslation.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "SpokenLanguageTranslation.name": %w`, err)} + } + } + if _u.mutation.LanguageCleared() && len(_u.mutation.LanguageIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "SpokenLanguageTranslation.language"`) + } + if _u.mutation.LocaleRecordCleared() && len(_u.mutation.LocaleRecordIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "SpokenLanguageTranslation.locale_record"`) + } + return nil +} + +func (_u *SpokenLanguageTranslationUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(spokenlanguagetranslation.Table, spokenlanguagetranslation.Columns, sqlgraph.NewFieldSpec(spokenlanguagetranslation.FieldLanguageCode, field.TypeString), sqlgraph.NewFieldSpec(spokenlanguagetranslation.FieldLocale, field.TypeString)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(spokenlanguagetranslation.FieldName, field.TypeString, value) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{spokenlanguagetranslation.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// SpokenLanguageTranslationUpdateOne is the builder for updating a single SpokenLanguageTranslation entity. +type SpokenLanguageTranslationUpdateOne struct { + config + fields []string + hooks []Hook + mutation *SpokenLanguageTranslationMutation +} + +// SetName sets the "name" field. +func (_u *SpokenLanguageTranslationUpdateOne) SetName(v string) *SpokenLanguageTranslationUpdateOne { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *SpokenLanguageTranslationUpdateOne) SetNillableName(v *string) *SpokenLanguageTranslationUpdateOne { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// Mutation returns the SpokenLanguageTranslationMutation object of the builder. +func (_u *SpokenLanguageTranslationUpdateOne) Mutation() *SpokenLanguageTranslationMutation { + return _u.mutation +} + +// Where appends a list predicates to the SpokenLanguageTranslationUpdate builder. +func (_u *SpokenLanguageTranslationUpdateOne) Where(ps ...predicate.SpokenLanguageTranslation) *SpokenLanguageTranslationUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *SpokenLanguageTranslationUpdateOne) Select(field string, fields ...string) *SpokenLanguageTranslationUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated SpokenLanguageTranslation entity. +func (_u *SpokenLanguageTranslationUpdateOne) Save(ctx context.Context) (*SpokenLanguageTranslation, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *SpokenLanguageTranslationUpdateOne) SaveX(ctx context.Context) *SpokenLanguageTranslation { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *SpokenLanguageTranslationUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *SpokenLanguageTranslationUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *SpokenLanguageTranslationUpdateOne) check() error { + if v, ok := _u.mutation.Name(); ok { + if err := spokenlanguagetranslation.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "SpokenLanguageTranslation.name": %w`, err)} + } + } + if _u.mutation.LanguageCleared() && len(_u.mutation.LanguageIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "SpokenLanguageTranslation.language"`) + } + if _u.mutation.LocaleRecordCleared() && len(_u.mutation.LocaleRecordIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "SpokenLanguageTranslation.locale_record"`) + } + return nil +} + +func (_u *SpokenLanguageTranslationUpdateOne) sqlSave(ctx context.Context) (_node *SpokenLanguageTranslation, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(spokenlanguagetranslation.Table, spokenlanguagetranslation.Columns, sqlgraph.NewFieldSpec(spokenlanguagetranslation.FieldLanguageCode, field.TypeString), sqlgraph.NewFieldSpec(spokenlanguagetranslation.FieldLocale, field.TypeString)) + if id, ok := _u.mutation.LanguageCode(); !ok { + return nil, &ValidationError{Name: "language_code", err: errors.New(`ent: missing "SpokenLanguageTranslation.language_code" for update`)} + } else { + _spec.Node.CompositeID[0].Value = id + } + if id, ok := _u.mutation.Locale(); !ok { + return nil, &ValidationError{Name: "locale", err: errors.New(`ent: missing "SpokenLanguageTranslation.locale" for update`)} + } else { + _spec.Node.CompositeID[1].Value = id + } + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, len(fields)) + for i, f := range fields { + if !spokenlanguagetranslation.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + _spec.Node.Columns[i] = f + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(spokenlanguagetranslation.FieldName, field.TypeString, value) + } + _node = &SpokenLanguageTranslation{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{spokenlanguagetranslation.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/ent/supportedlocale.go b/backend/ent/supportedlocale.go new file mode 100644 index 0000000..480c871 --- /dev/null +++ b/backend/ent/supportedlocale.go @@ -0,0 +1,158 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" +) + +// SupportedLocale is the model entity for the SupportedLocale schema. +type SupportedLocale struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // Active holds the value of the "active" field. + Active bool `json:"active,omitempty"` + // SortOrder holds the value of the "sort_order" field. + SortOrder int `json:"sort_order,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the SupportedLocaleQuery when eager-loading is set. + Edges SupportedLocaleEdges `json:"edges"` + selectValues sql.SelectValues +} + +// SupportedLocaleEdges holds the relations/edges for other nodes in the graph. +type SupportedLocaleEdges struct { + // TranslatedCategories holds the value of the translated_categories edge. + TranslatedCategories []*ServiceCategory `json:"translated_categories,omitempty"` + // TranslatedLanguages holds the value of the translated_languages edge. + TranslatedLanguages []*SpokenLanguage `json:"translated_languages,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [2]bool +} + +// TranslatedCategoriesOrErr returns the TranslatedCategories value or an error if the edge +// was not loaded in eager-loading. +func (e SupportedLocaleEdges) TranslatedCategoriesOrErr() ([]*ServiceCategory, error) { + if e.loadedTypes[0] { + return e.TranslatedCategories, nil + } + return nil, &NotLoadedError{edge: "translated_categories"} +} + +// TranslatedLanguagesOrErr returns the TranslatedLanguages value or an error if the edge +// was not loaded in eager-loading. +func (e SupportedLocaleEdges) TranslatedLanguagesOrErr() ([]*SpokenLanguage, error) { + if e.loadedTypes[1] { + return e.TranslatedLanguages, nil + } + return nil, &NotLoadedError{edge: "translated_languages"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*SupportedLocale) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case supportedlocale.FieldActive: + values[i] = new(sql.NullBool) + case supportedlocale.FieldSortOrder: + values[i] = new(sql.NullInt64) + case supportedlocale.FieldID: + values[i] = new(sql.NullString) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the SupportedLocale fields. +func (_m *SupportedLocale) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case supportedlocale.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + _m.ID = value.String + } + case supportedlocale.FieldActive: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field active", values[i]) + } else if value.Valid { + _m.Active = value.Bool + } + case supportedlocale.FieldSortOrder: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field sort_order", values[i]) + } else if value.Valid { + _m.SortOrder = int(value.Int64) + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the SupportedLocale. +// This includes values selected through modifiers, order, etc. +func (_m *SupportedLocale) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QueryTranslatedCategories queries the "translated_categories" edge of the SupportedLocale entity. +func (_m *SupportedLocale) QueryTranslatedCategories() *ServiceCategoryQuery { + return NewSupportedLocaleClient(_m.config).QueryTranslatedCategories(_m) +} + +// QueryTranslatedLanguages queries the "translated_languages" edge of the SupportedLocale entity. +func (_m *SupportedLocale) QueryTranslatedLanguages() *SpokenLanguageQuery { + return NewSupportedLocaleClient(_m.config).QueryTranslatedLanguages(_m) +} + +// Update returns a builder for updating this SupportedLocale. +// Note that you need to call SupportedLocale.Unwrap() before calling this method if this SupportedLocale +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *SupportedLocale) Update() *SupportedLocaleUpdateOne { + return NewSupportedLocaleClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the SupportedLocale entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *SupportedLocale) Unwrap() *SupportedLocale { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: SupportedLocale is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *SupportedLocale) String() string { + var builder strings.Builder + builder.WriteString("SupportedLocale(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("active=") + builder.WriteString(fmt.Sprintf("%v", _m.Active)) + builder.WriteString(", ") + builder.WriteString("sort_order=") + builder.WriteString(fmt.Sprintf("%v", _m.SortOrder)) + builder.WriteByte(')') + return builder.String() +} + +// SupportedLocales is a parsable slice of SupportedLocale. +type SupportedLocales []*SupportedLocale diff --git a/backend/ent/supportedlocale/supportedlocale.go b/backend/ent/supportedlocale/supportedlocale.go new file mode 100644 index 0000000..1659cd9 --- /dev/null +++ b/backend/ent/supportedlocale/supportedlocale.go @@ -0,0 +1,130 @@ +// Code generated by ent, DO NOT EDIT. + +package supportedlocale + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the supportedlocale type in the database. + Label = "supported_locale" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldActive holds the string denoting the active field in the database. + FieldActive = "active" + // FieldSortOrder holds the string denoting the sort_order field in the database. + FieldSortOrder = "sort_order" + // EdgeTranslatedCategories holds the string denoting the translated_categories edge name in mutations. + EdgeTranslatedCategories = "translated_categories" + // EdgeTranslatedLanguages holds the string denoting the translated_languages edge name in mutations. + EdgeTranslatedLanguages = "translated_languages" + // Table holds the table name of the supportedlocale in the database. + Table = "supported_locales" + // TranslatedCategoriesTable is the table that holds the translated_categories relation/edge. The primary key declared below. + TranslatedCategoriesTable = "service_category_translations" + // TranslatedCategoriesInverseTable is the table name for the ServiceCategory entity. + // It exists in this package in order to avoid circular dependency with the "servicecategory" package. + TranslatedCategoriesInverseTable = "service_categories" + // TranslatedLanguagesTable is the table that holds the translated_languages relation/edge. The primary key declared below. + TranslatedLanguagesTable = "spoken_language_translations" + // TranslatedLanguagesInverseTable is the table name for the SpokenLanguage entity. + // It exists in this package in order to avoid circular dependency with the "spokenlanguage" package. + TranslatedLanguagesInverseTable = "spoken_languages" +) + +// Columns holds all SQL columns for supportedlocale fields. +var Columns = []string{ + FieldID, + FieldActive, + FieldSortOrder, +} + +var ( + // TranslatedCategoriesPrimaryKey and TranslatedCategoriesColumn2 are the table columns denoting the + // primary key for the translated_categories relation (M2M). + TranslatedCategoriesPrimaryKey = []string{"category_id", "locale"} + // TranslatedLanguagesPrimaryKey and TranslatedLanguagesColumn2 are the table columns denoting the + // primary key for the translated_languages relation (M2M). + TranslatedLanguagesPrimaryKey = []string{"language_code", "locale"} +) + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultActive holds the default value on creation for the "active" field. + DefaultActive bool + // SortOrderValidator is a validator for the "sort_order" field. It is called by the builders before save. + SortOrderValidator func(int) error + // IDValidator is a validator for the "id" field. It is called by the builders before save. + IDValidator func(string) error +) + +// OrderOption defines the ordering options for the SupportedLocale queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByActive orders the results by the active field. +func ByActive(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldActive, opts...).ToFunc() +} + +// BySortOrder orders the results by the sort_order field. +func BySortOrder(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSortOrder, opts...).ToFunc() +} + +// ByTranslatedCategoriesCount orders the results by translated_categories count. +func ByTranslatedCategoriesCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newTranslatedCategoriesStep(), opts...) + } +} + +// ByTranslatedCategories orders the results by translated_categories terms. +func ByTranslatedCategories(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newTranslatedCategoriesStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} + +// ByTranslatedLanguagesCount orders the results by translated_languages count. +func ByTranslatedLanguagesCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newTranslatedLanguagesStep(), opts...) + } +} + +// ByTranslatedLanguages orders the results by translated_languages terms. +func ByTranslatedLanguages(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newTranslatedLanguagesStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} +func newTranslatedCategoriesStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(TranslatedCategoriesInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, TranslatedCategoriesTable, TranslatedCategoriesPrimaryKey...), + ) +} +func newTranslatedLanguagesStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(TranslatedLanguagesInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, TranslatedLanguagesTable, TranslatedLanguagesPrimaryKey...), + ) +} diff --git a/backend/ent/supportedlocale/where.go b/backend/ent/supportedlocale/where.go new file mode 100644 index 0000000..40b3ffa --- /dev/null +++ b/backend/ent/supportedlocale/where.go @@ -0,0 +1,185 @@ +// Code generated by ent, DO NOT EDIT. + +package supportedlocale + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldContainsFold(FieldID, id)) +} + +// Active applies equality check predicate on the "active" field. It's identical to ActiveEQ. +func Active(v bool) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldEQ(FieldActive, v)) +} + +// SortOrder applies equality check predicate on the "sort_order" field. It's identical to SortOrderEQ. +func SortOrder(v int) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldEQ(FieldSortOrder, v)) +} + +// ActiveEQ applies the EQ predicate on the "active" field. +func ActiveEQ(v bool) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldEQ(FieldActive, v)) +} + +// ActiveNEQ applies the NEQ predicate on the "active" field. +func ActiveNEQ(v bool) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldNEQ(FieldActive, v)) +} + +// SortOrderEQ applies the EQ predicate on the "sort_order" field. +func SortOrderEQ(v int) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldEQ(FieldSortOrder, v)) +} + +// SortOrderNEQ applies the NEQ predicate on the "sort_order" field. +func SortOrderNEQ(v int) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldNEQ(FieldSortOrder, v)) +} + +// SortOrderIn applies the In predicate on the "sort_order" field. +func SortOrderIn(vs ...int) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldIn(FieldSortOrder, vs...)) +} + +// SortOrderNotIn applies the NotIn predicate on the "sort_order" field. +func SortOrderNotIn(vs ...int) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldNotIn(FieldSortOrder, vs...)) +} + +// SortOrderGT applies the GT predicate on the "sort_order" field. +func SortOrderGT(v int) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldGT(FieldSortOrder, v)) +} + +// SortOrderGTE applies the GTE predicate on the "sort_order" field. +func SortOrderGTE(v int) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldGTE(FieldSortOrder, v)) +} + +// SortOrderLT applies the LT predicate on the "sort_order" field. +func SortOrderLT(v int) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldLT(FieldSortOrder, v)) +} + +// SortOrderLTE applies the LTE predicate on the "sort_order" field. +func SortOrderLTE(v int) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.FieldLTE(FieldSortOrder, v)) +} + +// HasTranslatedCategories applies the HasEdge predicate on the "translated_categories" edge. +func HasTranslatedCategories() predicate.SupportedLocale { + return predicate.SupportedLocale(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, TranslatedCategoriesTable, TranslatedCategoriesPrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasTranslatedCategoriesWith applies the HasEdge predicate on the "translated_categories" edge with a given conditions (other predicates). +func HasTranslatedCategoriesWith(preds ...predicate.ServiceCategory) predicate.SupportedLocale { + return predicate.SupportedLocale(func(s *sql.Selector) { + step := newTranslatedCategoriesStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasTranslatedLanguages applies the HasEdge predicate on the "translated_languages" edge. +func HasTranslatedLanguages() predicate.SupportedLocale { + return predicate.SupportedLocale(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, TranslatedLanguagesTable, TranslatedLanguagesPrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasTranslatedLanguagesWith applies the HasEdge predicate on the "translated_languages" edge with a given conditions (other predicates). +func HasTranslatedLanguagesWith(preds ...predicate.SpokenLanguage) predicate.SupportedLocale { + return predicate.SupportedLocale(func(s *sql.Selector) { + step := newTranslatedLanguagesStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.SupportedLocale) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.SupportedLocale) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.SupportedLocale) predicate.SupportedLocale { + return predicate.SupportedLocale(sql.NotPredicates(p)) +} diff --git a/backend/ent/supportedlocale_create.go b/backend/ent/supportedlocale_create.go new file mode 100644 index 0000000..bde0708 --- /dev/null +++ b/backend/ent/supportedlocale_create.go @@ -0,0 +1,300 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" + "github.com/google/uuid" +) + +// SupportedLocaleCreate is the builder for creating a SupportedLocale entity. +type SupportedLocaleCreate struct { + config + mutation *SupportedLocaleMutation + hooks []Hook +} + +// SetActive sets the "active" field. +func (_c *SupportedLocaleCreate) SetActive(v bool) *SupportedLocaleCreate { + _c.mutation.SetActive(v) + return _c +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_c *SupportedLocaleCreate) SetNillableActive(v *bool) *SupportedLocaleCreate { + if v != nil { + _c.SetActive(*v) + } + return _c +} + +// SetSortOrder sets the "sort_order" field. +func (_c *SupportedLocaleCreate) SetSortOrder(v int) *SupportedLocaleCreate { + _c.mutation.SetSortOrder(v) + return _c +} + +// SetID sets the "id" field. +func (_c *SupportedLocaleCreate) SetID(v string) *SupportedLocaleCreate { + _c.mutation.SetID(v) + return _c +} + +// AddTranslatedCategoryIDs adds the "translated_categories" edge to the ServiceCategory entity by IDs. +func (_c *SupportedLocaleCreate) AddTranslatedCategoryIDs(ids ...uuid.UUID) *SupportedLocaleCreate { + _c.mutation.AddTranslatedCategoryIDs(ids...) + return _c +} + +// AddTranslatedCategories adds the "translated_categories" edges to the ServiceCategory entity. +func (_c *SupportedLocaleCreate) AddTranslatedCategories(v ...*ServiceCategory) *SupportedLocaleCreate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddTranslatedCategoryIDs(ids...) +} + +// AddTranslatedLanguageIDs adds the "translated_languages" edge to the SpokenLanguage entity by IDs. +func (_c *SupportedLocaleCreate) AddTranslatedLanguageIDs(ids ...string) *SupportedLocaleCreate { + _c.mutation.AddTranslatedLanguageIDs(ids...) + return _c +} + +// AddTranslatedLanguages adds the "translated_languages" edges to the SpokenLanguage entity. +func (_c *SupportedLocaleCreate) AddTranslatedLanguages(v ...*SpokenLanguage) *SupportedLocaleCreate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddTranslatedLanguageIDs(ids...) +} + +// Mutation returns the SupportedLocaleMutation object of the builder. +func (_c *SupportedLocaleCreate) Mutation() *SupportedLocaleMutation { + return _c.mutation +} + +// Save creates the SupportedLocale in the database. +func (_c *SupportedLocaleCreate) Save(ctx context.Context) (*SupportedLocale, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *SupportedLocaleCreate) SaveX(ctx context.Context) *SupportedLocale { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *SupportedLocaleCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *SupportedLocaleCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *SupportedLocaleCreate) defaults() { + if _, ok := _c.mutation.Active(); !ok { + v := supportedlocale.DefaultActive + _c.mutation.SetActive(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *SupportedLocaleCreate) check() error { + if _, ok := _c.mutation.Active(); !ok { + return &ValidationError{Name: "active", err: errors.New(`ent: missing required field "SupportedLocale.active"`)} + } + if _, ok := _c.mutation.SortOrder(); !ok { + return &ValidationError{Name: "sort_order", err: errors.New(`ent: missing required field "SupportedLocale.sort_order"`)} + } + if v, ok := _c.mutation.SortOrder(); ok { + if err := supportedlocale.SortOrderValidator(v); err != nil { + return &ValidationError{Name: "sort_order", err: fmt.Errorf(`ent: validator failed for field "SupportedLocale.sort_order": %w`, err)} + } + } + if v, ok := _c.mutation.ID(); ok { + if err := supportedlocale.IDValidator(v); err != nil { + return &ValidationError{Name: "id", err: fmt.Errorf(`ent: validator failed for field "SupportedLocale.id": %w`, err)} + } + } + return nil +} + +func (_c *SupportedLocaleCreate) sqlSave(ctx context.Context) (*SupportedLocale, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected SupportedLocale.ID type: %T", _spec.ID.Value) + } + } + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *SupportedLocaleCreate) createSpec() (*SupportedLocale, *sqlgraph.CreateSpec) { + var ( + _node = &SupportedLocale{config: _c.config} + _spec = sqlgraph.NewCreateSpec(supportedlocale.Table, sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString)) + ) + if id, ok := _c.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := _c.mutation.Active(); ok { + _spec.SetField(supportedlocale.FieldActive, field.TypeBool, value) + _node.Active = value + } + if value, ok := _c.mutation.SortOrder(); ok { + _spec.SetField(supportedlocale.FieldSortOrder, field.TypeInt, value) + _node.SortOrder = value + } + if nodes := _c.mutation.TranslatedCategoriesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedCategoriesTable, + Columns: supportedlocale.TranslatedCategoriesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.TranslatedLanguagesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedLanguagesTable, + Columns: supportedlocale.TranslatedLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// SupportedLocaleCreateBulk is the builder for creating many SupportedLocale entities in bulk. +type SupportedLocaleCreateBulk struct { + config + err error + builders []*SupportedLocaleCreate +} + +// Save creates the SupportedLocale entities in the database. +func (_c *SupportedLocaleCreateBulk) Save(ctx context.Context) ([]*SupportedLocale, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*SupportedLocale, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*SupportedLocaleMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *SupportedLocaleCreateBulk) SaveX(ctx context.Context) []*SupportedLocale { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *SupportedLocaleCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *SupportedLocaleCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/supportedlocale_delete.go b/backend/ent/supportedlocale_delete.go new file mode 100644 index 0000000..1803f91 --- /dev/null +++ b/backend/ent/supportedlocale_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" +) + +// SupportedLocaleDelete is the builder for deleting a SupportedLocale entity. +type SupportedLocaleDelete struct { + config + hooks []Hook + mutation *SupportedLocaleMutation +} + +// Where appends a list predicates to the SupportedLocaleDelete builder. +func (_d *SupportedLocaleDelete) Where(ps ...predicate.SupportedLocale) *SupportedLocaleDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *SupportedLocaleDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *SupportedLocaleDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *SupportedLocaleDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(supportedlocale.Table, sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// SupportedLocaleDeleteOne is the builder for deleting a single SupportedLocale entity. +type SupportedLocaleDeleteOne struct { + _d *SupportedLocaleDelete +} + +// Where appends a list predicates to the SupportedLocaleDelete builder. +func (_d *SupportedLocaleDeleteOne) Where(ps ...predicate.SupportedLocale) *SupportedLocaleDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *SupportedLocaleDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{supportedlocale.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *SupportedLocaleDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/supportedlocale_query.go b/backend/ent/supportedlocale_query.go new file mode 100644 index 0000000..55aaa1e --- /dev/null +++ b/backend/ent/supportedlocale_query.go @@ -0,0 +1,747 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "database/sql/driver" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" + "github.com/google/uuid" +) + +// SupportedLocaleQuery is the builder for querying SupportedLocale entities. +type SupportedLocaleQuery struct { + config + ctx *QueryContext + order []supportedlocale.OrderOption + inters []Interceptor + predicates []predicate.SupportedLocale + withTranslatedCategories *ServiceCategoryQuery + withTranslatedLanguages *SpokenLanguageQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the SupportedLocaleQuery builder. +func (_q *SupportedLocaleQuery) Where(ps ...predicate.SupportedLocale) *SupportedLocaleQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *SupportedLocaleQuery) Limit(limit int) *SupportedLocaleQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *SupportedLocaleQuery) Offset(offset int) *SupportedLocaleQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *SupportedLocaleQuery) Unique(unique bool) *SupportedLocaleQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *SupportedLocaleQuery) Order(o ...supportedlocale.OrderOption) *SupportedLocaleQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QueryTranslatedCategories chains the current query on the "translated_categories" edge. +func (_q *SupportedLocaleQuery) QueryTranslatedCategories() *ServiceCategoryQuery { + query := (&ServiceCategoryClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(supportedlocale.Table, supportedlocale.FieldID, selector), + sqlgraph.To(servicecategory.Table, servicecategory.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, supportedlocale.TranslatedCategoriesTable, supportedlocale.TranslatedCategoriesPrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryTranslatedLanguages chains the current query on the "translated_languages" edge. +func (_q *SupportedLocaleQuery) QueryTranslatedLanguages() *SpokenLanguageQuery { + query := (&SpokenLanguageClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(supportedlocale.Table, supportedlocale.FieldID, selector), + sqlgraph.To(spokenlanguage.Table, spokenlanguage.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, supportedlocale.TranslatedLanguagesTable, supportedlocale.TranslatedLanguagesPrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first SupportedLocale entity from the query. +// Returns a *NotFoundError when no SupportedLocale was found. +func (_q *SupportedLocaleQuery) First(ctx context.Context) (*SupportedLocale, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{supportedlocale.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *SupportedLocaleQuery) FirstX(ctx context.Context) *SupportedLocale { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first SupportedLocale ID from the query. +// Returns a *NotFoundError when no SupportedLocale ID was found. +func (_q *SupportedLocaleQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{supportedlocale.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *SupportedLocaleQuery) FirstIDX(ctx context.Context) string { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single SupportedLocale entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one SupportedLocale entity is found. +// Returns a *NotFoundError when no SupportedLocale entities are found. +func (_q *SupportedLocaleQuery) Only(ctx context.Context) (*SupportedLocale, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{supportedlocale.Label} + default: + return nil, &NotSingularError{supportedlocale.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *SupportedLocaleQuery) OnlyX(ctx context.Context) *SupportedLocale { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only SupportedLocale ID in the query. +// Returns a *NotSingularError when more than one SupportedLocale ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *SupportedLocaleQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{supportedlocale.Label} + default: + err = &NotSingularError{supportedlocale.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *SupportedLocaleQuery) OnlyIDX(ctx context.Context) string { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of SupportedLocales. +func (_q *SupportedLocaleQuery) All(ctx context.Context) ([]*SupportedLocale, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*SupportedLocale, *SupportedLocaleQuery]() + return withInterceptors[[]*SupportedLocale](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *SupportedLocaleQuery) AllX(ctx context.Context) []*SupportedLocale { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of SupportedLocale IDs. +func (_q *SupportedLocaleQuery) IDs(ctx context.Context) (ids []string, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(supportedlocale.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *SupportedLocaleQuery) IDsX(ctx context.Context) []string { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *SupportedLocaleQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*SupportedLocaleQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *SupportedLocaleQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *SupportedLocaleQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *SupportedLocaleQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the SupportedLocaleQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *SupportedLocaleQuery) Clone() *SupportedLocaleQuery { + if _q == nil { + return nil + } + return &SupportedLocaleQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]supportedlocale.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.SupportedLocale{}, _q.predicates...), + withTranslatedCategories: _q.withTranslatedCategories.Clone(), + withTranslatedLanguages: _q.withTranslatedLanguages.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// WithTranslatedCategories tells the query-builder to eager-load the nodes that are connected to +// the "translated_categories" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *SupportedLocaleQuery) WithTranslatedCategories(opts ...func(*ServiceCategoryQuery)) *SupportedLocaleQuery { + query := (&ServiceCategoryClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withTranslatedCategories = query + return _q +} + +// WithTranslatedLanguages tells the query-builder to eager-load the nodes that are connected to +// the "translated_languages" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *SupportedLocaleQuery) WithTranslatedLanguages(opts ...func(*SpokenLanguageQuery)) *SupportedLocaleQuery { + query := (&SpokenLanguageClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withTranslatedLanguages = query + return _q +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// Active bool `json:"active,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.SupportedLocale.Query(). +// GroupBy(supportedlocale.FieldActive). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *SupportedLocaleQuery) GroupBy(field string, fields ...string) *SupportedLocaleGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &SupportedLocaleGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = supportedlocale.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// Active bool `json:"active,omitempty"` +// } +// +// client.SupportedLocale.Query(). +// Select(supportedlocale.FieldActive). +// Scan(ctx, &v) +func (_q *SupportedLocaleQuery) Select(fields ...string) *SupportedLocaleSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &SupportedLocaleSelect{SupportedLocaleQuery: _q} + sbuild.label = supportedlocale.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a SupportedLocaleSelect configured with the given aggregations. +func (_q *SupportedLocaleQuery) Aggregate(fns ...AggregateFunc) *SupportedLocaleSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *SupportedLocaleQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !supportedlocale.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *SupportedLocaleQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*SupportedLocale, error) { + var ( + nodes = []*SupportedLocale{} + _spec = _q.querySpec() + loadedTypes = [2]bool{ + _q.withTranslatedCategories != nil, + _q.withTranslatedLanguages != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*SupportedLocale).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &SupportedLocale{config: _q.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := _q.withTranslatedCategories; query != nil { + if err := _q.loadTranslatedCategories(ctx, query, nodes, + func(n *SupportedLocale) { n.Edges.TranslatedCategories = []*ServiceCategory{} }, + func(n *SupportedLocale, e *ServiceCategory) { + n.Edges.TranslatedCategories = append(n.Edges.TranslatedCategories, e) + }); err != nil { + return nil, err + } + } + if query := _q.withTranslatedLanguages; query != nil { + if err := _q.loadTranslatedLanguages(ctx, query, nodes, + func(n *SupportedLocale) { n.Edges.TranslatedLanguages = []*SpokenLanguage{} }, + func(n *SupportedLocale, e *SpokenLanguage) { + n.Edges.TranslatedLanguages = append(n.Edges.TranslatedLanguages, e) + }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *SupportedLocaleQuery) loadTranslatedCategories(ctx context.Context, query *ServiceCategoryQuery, nodes []*SupportedLocale, init func(*SupportedLocale), assign func(*SupportedLocale, *ServiceCategory)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[string]*SupportedLocale) + nids := make(map[uuid.UUID]map[*SupportedLocale]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(supportedlocale.TranslatedCategoriesTable) + s.Join(joinT).On(s.C(servicecategory.FieldID), joinT.C(supportedlocale.TranslatedCategoriesPrimaryKey[0])) + s.Where(sql.InValues(joinT.C(supportedlocale.TranslatedCategoriesPrimaryKey[1]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(supportedlocale.TranslatedCategoriesPrimaryKey[1])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(sql.NullString)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := values[0].(*sql.NullString).String + inValue := *values[1].(*uuid.UUID) + if nids[inValue] == nil { + nids[inValue] = map[*SupportedLocale]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*ServiceCategory](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "translated_categories" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} +func (_q *SupportedLocaleQuery) loadTranslatedLanguages(ctx context.Context, query *SpokenLanguageQuery, nodes []*SupportedLocale, init func(*SupportedLocale), assign func(*SupportedLocale, *SpokenLanguage)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[string]*SupportedLocale) + nids := make(map[string]map[*SupportedLocale]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(supportedlocale.TranslatedLanguagesTable) + s.Join(joinT).On(s.C(spokenlanguage.FieldID), joinT.C(supportedlocale.TranslatedLanguagesPrimaryKey[0])) + s.Where(sql.InValues(joinT.C(supportedlocale.TranslatedLanguagesPrimaryKey[1]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(supportedlocale.TranslatedLanguagesPrimaryKey[1])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(sql.NullString)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := values[0].(*sql.NullString).String + inValue := values[1].(*sql.NullString).String + if nids[inValue] == nil { + nids[inValue] = map[*SupportedLocale]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*SpokenLanguage](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "translated_languages" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} + +func (_q *SupportedLocaleQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *SupportedLocaleQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(supportedlocale.Table, supportedlocale.Columns, sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, supportedlocale.FieldID) + for i := range fields { + if fields[i] != supportedlocale.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *SupportedLocaleQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(supportedlocale.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = supportedlocale.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// SupportedLocaleGroupBy is the group-by builder for SupportedLocale entities. +type SupportedLocaleGroupBy struct { + selector + build *SupportedLocaleQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *SupportedLocaleGroupBy) Aggregate(fns ...AggregateFunc) *SupportedLocaleGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *SupportedLocaleGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*SupportedLocaleQuery, *SupportedLocaleGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *SupportedLocaleGroupBy) sqlScan(ctx context.Context, root *SupportedLocaleQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// SupportedLocaleSelect is the builder for selecting fields of SupportedLocale entities. +type SupportedLocaleSelect struct { + *SupportedLocaleQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *SupportedLocaleSelect) Aggregate(fns ...AggregateFunc) *SupportedLocaleSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *SupportedLocaleSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*SupportedLocaleQuery, *SupportedLocaleSelect](ctx, _s.SupportedLocaleQuery, _s, _s.inters, v) +} + +func (_s *SupportedLocaleSelect) sqlScan(ctx context.Context, root *SupportedLocaleQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/supportedlocale_update.go b/backend/ent/supportedlocale_update.go new file mode 100644 index 0000000..5834dff --- /dev/null +++ b/backend/ent/supportedlocale_update.go @@ -0,0 +1,616 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/predicate" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/servicecategory" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/supportedlocale" + "github.com/google/uuid" +) + +// SupportedLocaleUpdate is the builder for updating SupportedLocale entities. +type SupportedLocaleUpdate struct { + config + hooks []Hook + mutation *SupportedLocaleMutation +} + +// Where appends a list predicates to the SupportedLocaleUpdate builder. +func (_u *SupportedLocaleUpdate) Where(ps ...predicate.SupportedLocale) *SupportedLocaleUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetActive sets the "active" field. +func (_u *SupportedLocaleUpdate) SetActive(v bool) *SupportedLocaleUpdate { + _u.mutation.SetActive(v) + return _u +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_u *SupportedLocaleUpdate) SetNillableActive(v *bool) *SupportedLocaleUpdate { + if v != nil { + _u.SetActive(*v) + } + return _u +} + +// SetSortOrder sets the "sort_order" field. +func (_u *SupportedLocaleUpdate) SetSortOrder(v int) *SupportedLocaleUpdate { + _u.mutation.ResetSortOrder() + _u.mutation.SetSortOrder(v) + return _u +} + +// SetNillableSortOrder sets the "sort_order" field if the given value is not nil. +func (_u *SupportedLocaleUpdate) SetNillableSortOrder(v *int) *SupportedLocaleUpdate { + if v != nil { + _u.SetSortOrder(*v) + } + return _u +} + +// AddSortOrder adds value to the "sort_order" field. +func (_u *SupportedLocaleUpdate) AddSortOrder(v int) *SupportedLocaleUpdate { + _u.mutation.AddSortOrder(v) + return _u +} + +// AddTranslatedCategoryIDs adds the "translated_categories" edge to the ServiceCategory entity by IDs. +func (_u *SupportedLocaleUpdate) AddTranslatedCategoryIDs(ids ...uuid.UUID) *SupportedLocaleUpdate { + _u.mutation.AddTranslatedCategoryIDs(ids...) + return _u +} + +// AddTranslatedCategories adds the "translated_categories" edges to the ServiceCategory entity. +func (_u *SupportedLocaleUpdate) AddTranslatedCategories(v ...*ServiceCategory) *SupportedLocaleUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddTranslatedCategoryIDs(ids...) +} + +// AddTranslatedLanguageIDs adds the "translated_languages" edge to the SpokenLanguage entity by IDs. +func (_u *SupportedLocaleUpdate) AddTranslatedLanguageIDs(ids ...string) *SupportedLocaleUpdate { + _u.mutation.AddTranslatedLanguageIDs(ids...) + return _u +} + +// AddTranslatedLanguages adds the "translated_languages" edges to the SpokenLanguage entity. +func (_u *SupportedLocaleUpdate) AddTranslatedLanguages(v ...*SpokenLanguage) *SupportedLocaleUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddTranslatedLanguageIDs(ids...) +} + +// Mutation returns the SupportedLocaleMutation object of the builder. +func (_u *SupportedLocaleUpdate) Mutation() *SupportedLocaleMutation { + return _u.mutation +} + +// ClearTranslatedCategories clears all "translated_categories" edges to the ServiceCategory entity. +func (_u *SupportedLocaleUpdate) ClearTranslatedCategories() *SupportedLocaleUpdate { + _u.mutation.ClearTranslatedCategories() + return _u +} + +// RemoveTranslatedCategoryIDs removes the "translated_categories" edge to ServiceCategory entities by IDs. +func (_u *SupportedLocaleUpdate) RemoveTranslatedCategoryIDs(ids ...uuid.UUID) *SupportedLocaleUpdate { + _u.mutation.RemoveTranslatedCategoryIDs(ids...) + return _u +} + +// RemoveTranslatedCategories removes "translated_categories" edges to ServiceCategory entities. +func (_u *SupportedLocaleUpdate) RemoveTranslatedCategories(v ...*ServiceCategory) *SupportedLocaleUpdate { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveTranslatedCategoryIDs(ids...) +} + +// ClearTranslatedLanguages clears all "translated_languages" edges to the SpokenLanguage entity. +func (_u *SupportedLocaleUpdate) ClearTranslatedLanguages() *SupportedLocaleUpdate { + _u.mutation.ClearTranslatedLanguages() + return _u +} + +// RemoveTranslatedLanguageIDs removes the "translated_languages" edge to SpokenLanguage entities by IDs. +func (_u *SupportedLocaleUpdate) RemoveTranslatedLanguageIDs(ids ...string) *SupportedLocaleUpdate { + _u.mutation.RemoveTranslatedLanguageIDs(ids...) + return _u +} + +// RemoveTranslatedLanguages removes "translated_languages" edges to SpokenLanguage entities. +func (_u *SupportedLocaleUpdate) RemoveTranslatedLanguages(v ...*SpokenLanguage) *SupportedLocaleUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveTranslatedLanguageIDs(ids...) +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *SupportedLocaleUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *SupportedLocaleUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *SupportedLocaleUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *SupportedLocaleUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *SupportedLocaleUpdate) check() error { + if v, ok := _u.mutation.SortOrder(); ok { + if err := supportedlocale.SortOrderValidator(v); err != nil { + return &ValidationError{Name: "sort_order", err: fmt.Errorf(`ent: validator failed for field "SupportedLocale.sort_order": %w`, err)} + } + } + return nil +} + +func (_u *SupportedLocaleUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(supportedlocale.Table, supportedlocale.Columns, sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Active(); ok { + _spec.SetField(supportedlocale.FieldActive, field.TypeBool, value) + } + if value, ok := _u.mutation.SortOrder(); ok { + _spec.SetField(supportedlocale.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedSortOrder(); ok { + _spec.AddField(supportedlocale.FieldSortOrder, field.TypeInt, value) + } + if _u.mutation.TranslatedCategoriesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedCategoriesTable, + Columns: supportedlocale.TranslatedCategoriesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedTranslatedCategoriesIDs(); len(nodes) > 0 && !_u.mutation.TranslatedCategoriesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedCategoriesTable, + Columns: supportedlocale.TranslatedCategoriesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.TranslatedCategoriesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedCategoriesTable, + Columns: supportedlocale.TranslatedCategoriesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _u.mutation.TranslatedLanguagesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedLanguagesTable, + Columns: supportedlocale.TranslatedLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedTranslatedLanguagesIDs(); len(nodes) > 0 && !_u.mutation.TranslatedLanguagesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedLanguagesTable, + Columns: supportedlocale.TranslatedLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.TranslatedLanguagesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedLanguagesTable, + Columns: supportedlocale.TranslatedLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{supportedlocale.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// SupportedLocaleUpdateOne is the builder for updating a single SupportedLocale entity. +type SupportedLocaleUpdateOne struct { + config + fields []string + hooks []Hook + mutation *SupportedLocaleMutation +} + +// SetActive sets the "active" field. +func (_u *SupportedLocaleUpdateOne) SetActive(v bool) *SupportedLocaleUpdateOne { + _u.mutation.SetActive(v) + return _u +} + +// SetNillableActive sets the "active" field if the given value is not nil. +func (_u *SupportedLocaleUpdateOne) SetNillableActive(v *bool) *SupportedLocaleUpdateOne { + if v != nil { + _u.SetActive(*v) + } + return _u +} + +// SetSortOrder sets the "sort_order" field. +func (_u *SupportedLocaleUpdateOne) SetSortOrder(v int) *SupportedLocaleUpdateOne { + _u.mutation.ResetSortOrder() + _u.mutation.SetSortOrder(v) + return _u +} + +// SetNillableSortOrder sets the "sort_order" field if the given value is not nil. +func (_u *SupportedLocaleUpdateOne) SetNillableSortOrder(v *int) *SupportedLocaleUpdateOne { + if v != nil { + _u.SetSortOrder(*v) + } + return _u +} + +// AddSortOrder adds value to the "sort_order" field. +func (_u *SupportedLocaleUpdateOne) AddSortOrder(v int) *SupportedLocaleUpdateOne { + _u.mutation.AddSortOrder(v) + return _u +} + +// AddTranslatedCategoryIDs adds the "translated_categories" edge to the ServiceCategory entity by IDs. +func (_u *SupportedLocaleUpdateOne) AddTranslatedCategoryIDs(ids ...uuid.UUID) *SupportedLocaleUpdateOne { + _u.mutation.AddTranslatedCategoryIDs(ids...) + return _u +} + +// AddTranslatedCategories adds the "translated_categories" edges to the ServiceCategory entity. +func (_u *SupportedLocaleUpdateOne) AddTranslatedCategories(v ...*ServiceCategory) *SupportedLocaleUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddTranslatedCategoryIDs(ids...) +} + +// AddTranslatedLanguageIDs adds the "translated_languages" edge to the SpokenLanguage entity by IDs. +func (_u *SupportedLocaleUpdateOne) AddTranslatedLanguageIDs(ids ...string) *SupportedLocaleUpdateOne { + _u.mutation.AddTranslatedLanguageIDs(ids...) + return _u +} + +// AddTranslatedLanguages adds the "translated_languages" edges to the SpokenLanguage entity. +func (_u *SupportedLocaleUpdateOne) AddTranslatedLanguages(v ...*SpokenLanguage) *SupportedLocaleUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddTranslatedLanguageIDs(ids...) +} + +// Mutation returns the SupportedLocaleMutation object of the builder. +func (_u *SupportedLocaleUpdateOne) Mutation() *SupportedLocaleMutation { + return _u.mutation +} + +// ClearTranslatedCategories clears all "translated_categories" edges to the ServiceCategory entity. +func (_u *SupportedLocaleUpdateOne) ClearTranslatedCategories() *SupportedLocaleUpdateOne { + _u.mutation.ClearTranslatedCategories() + return _u +} + +// RemoveTranslatedCategoryIDs removes the "translated_categories" edge to ServiceCategory entities by IDs. +func (_u *SupportedLocaleUpdateOne) RemoveTranslatedCategoryIDs(ids ...uuid.UUID) *SupportedLocaleUpdateOne { + _u.mutation.RemoveTranslatedCategoryIDs(ids...) + return _u +} + +// RemoveTranslatedCategories removes "translated_categories" edges to ServiceCategory entities. +func (_u *SupportedLocaleUpdateOne) RemoveTranslatedCategories(v ...*ServiceCategory) *SupportedLocaleUpdateOne { + ids := make([]uuid.UUID, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveTranslatedCategoryIDs(ids...) +} + +// ClearTranslatedLanguages clears all "translated_languages" edges to the SpokenLanguage entity. +func (_u *SupportedLocaleUpdateOne) ClearTranslatedLanguages() *SupportedLocaleUpdateOne { + _u.mutation.ClearTranslatedLanguages() + return _u +} + +// RemoveTranslatedLanguageIDs removes the "translated_languages" edge to SpokenLanguage entities by IDs. +func (_u *SupportedLocaleUpdateOne) RemoveTranslatedLanguageIDs(ids ...string) *SupportedLocaleUpdateOne { + _u.mutation.RemoveTranslatedLanguageIDs(ids...) + return _u +} + +// RemoveTranslatedLanguages removes "translated_languages" edges to SpokenLanguage entities. +func (_u *SupportedLocaleUpdateOne) RemoveTranslatedLanguages(v ...*SpokenLanguage) *SupportedLocaleUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveTranslatedLanguageIDs(ids...) +} + +// Where appends a list predicates to the SupportedLocaleUpdate builder. +func (_u *SupportedLocaleUpdateOne) Where(ps ...predicate.SupportedLocale) *SupportedLocaleUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *SupportedLocaleUpdateOne) Select(field string, fields ...string) *SupportedLocaleUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated SupportedLocale entity. +func (_u *SupportedLocaleUpdateOne) Save(ctx context.Context) (*SupportedLocale, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *SupportedLocaleUpdateOne) SaveX(ctx context.Context) *SupportedLocale { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *SupportedLocaleUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *SupportedLocaleUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *SupportedLocaleUpdateOne) check() error { + if v, ok := _u.mutation.SortOrder(); ok { + if err := supportedlocale.SortOrderValidator(v); err != nil { + return &ValidationError{Name: "sort_order", err: fmt.Errorf(`ent: validator failed for field "SupportedLocale.sort_order": %w`, err)} + } + } + return nil +} + +func (_u *SupportedLocaleUpdateOne) sqlSave(ctx context.Context) (_node *SupportedLocale, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(supportedlocale.Table, supportedlocale.Columns, sqlgraph.NewFieldSpec(supportedlocale.FieldID, field.TypeString)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "SupportedLocale.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, supportedlocale.FieldID) + for _, f := range fields { + if !supportedlocale.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != supportedlocale.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Active(); ok { + _spec.SetField(supportedlocale.FieldActive, field.TypeBool, value) + } + if value, ok := _u.mutation.SortOrder(); ok { + _spec.SetField(supportedlocale.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedSortOrder(); ok { + _spec.AddField(supportedlocale.FieldSortOrder, field.TypeInt, value) + } + if _u.mutation.TranslatedCategoriesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedCategoriesTable, + Columns: supportedlocale.TranslatedCategoriesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedTranslatedCategoriesIDs(); len(nodes) > 0 && !_u.mutation.TranslatedCategoriesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedCategoriesTable, + Columns: supportedlocale.TranslatedCategoriesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.TranslatedCategoriesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedCategoriesTable, + Columns: supportedlocale.TranslatedCategoriesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(servicecategory.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _u.mutation.TranslatedLanguagesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedLanguagesTable, + Columns: supportedlocale.TranslatedLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedTranslatedLanguagesIDs(); len(nodes) > 0 && !_u.mutation.TranslatedLanguagesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedLanguagesTable, + Columns: supportedlocale.TranslatedLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.TranslatedLanguagesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: true, + Table: supportedlocale.TranslatedLanguagesTable, + Columns: supportedlocale.TranslatedLanguagesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(spokenlanguage.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &SupportedLocale{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{supportedlocale.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/ent/tx.go b/backend/ent/tx.go index 155e778..855c544 100644 --- a/backend/ent/tx.go +++ b/backend/ent/tx.go @@ -12,8 +12,28 @@ import ( // Tx is a transactional client that is created by calling Client.Tx(). type Tx struct { config + // AdministrativeArea is the client for interacting with the AdministrativeArea builders. + AdministrativeArea *AdministrativeAreaClient // InternalUser is the client for interacting with the InternalUser builders. InternalUser *InternalUserClient + // Locality is the client for interacting with the Locality builders. + Locality *LocalityClient + // ProviderProfile is the client for interacting with the ProviderProfile builders. + ProviderProfile *ProviderProfileClient + // ProviderServiceLocality is the client for interacting with the ProviderServiceLocality builders. + ProviderServiceLocality *ProviderServiceLocalityClient + // ProviderSpokenLanguage is the client for interacting with the ProviderSpokenLanguage builders. + ProviderSpokenLanguage *ProviderSpokenLanguageClient + // ServiceCategory is the client for interacting with the ServiceCategory builders. + ServiceCategory *ServiceCategoryClient + // ServiceCategoryTranslation is the client for interacting with the ServiceCategoryTranslation builders. + ServiceCategoryTranslation *ServiceCategoryTranslationClient + // SpokenLanguage is the client for interacting with the SpokenLanguage builders. + SpokenLanguage *SpokenLanguageClient + // SpokenLanguageTranslation is the client for interacting with the SpokenLanguageTranslation builders. + SpokenLanguageTranslation *SpokenLanguageTranslationClient + // SupportedLocale is the client for interacting with the SupportedLocale builders. + SupportedLocale *SupportedLocaleClient // UserAccount is the client for interacting with the UserAccount builders. UserAccount *UserAccountClient @@ -147,7 +167,17 @@ func (tx *Tx) Client() *Client { } func (tx *Tx) init() { + tx.AdministrativeArea = NewAdministrativeAreaClient(tx.config) tx.InternalUser = NewInternalUserClient(tx.config) + tx.Locality = NewLocalityClient(tx.config) + tx.ProviderProfile = NewProviderProfileClient(tx.config) + tx.ProviderServiceLocality = NewProviderServiceLocalityClient(tx.config) + tx.ProviderSpokenLanguage = NewProviderSpokenLanguageClient(tx.config) + tx.ServiceCategory = NewServiceCategoryClient(tx.config) + tx.ServiceCategoryTranslation = NewServiceCategoryTranslationClient(tx.config) + tx.SpokenLanguage = NewSpokenLanguageClient(tx.config) + tx.SpokenLanguageTranslation = NewSpokenLanguageTranslationClient(tx.config) + tx.SupportedLocale = NewSupportedLocaleClient(tx.config) tx.UserAccount = NewUserAccountClient(tx.config) } @@ -158,7 +188,7 @@ func (tx *Tx) init() { // of them in order to commit or rollback the transaction. // // If a closed transaction is embedded in one of the generated entities, and the entity -// applies a query, for example: InternalUser.QueryXXX(), the query will be executed +// applies a query, for example: AdministrativeArea.QueryXXX(), the query will be executed // through the driver which created this transaction. // // Note that txDriver is not goroutine safe. diff --git a/backend/internal/users/migration_contract_test.go b/backend/internal/users/migration_contract_test.go index c02f91b..c6ad949 100644 --- a/backend/internal/users/migration_contract_test.go +++ b/backend/internal/users/migration_contract_test.go @@ -101,3 +101,85 @@ func TestCreateUserAccountsMigrationContract(t *testing.T) { } } } + +func TestCreateTaxonomyLocationsProviderProfilesMigrationContract(t *testing.T) { + t.Parallel() + + migrationDirectory := filepath.Join("..", "..", "..", "supabase", "migrations") + entries, err := os.ReadDir(migrationDirectory) + if err != nil { + t.Fatalf("read migration directory: %v", err) + } + + var migrationPath string + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), "_create_taxonomy_locations_provider_profiles.sql") { + migrationPath = filepath.Join(migrationDirectory, entry.Name()) + break + } + } + if migrationPath == "" { + t.Fatal("taxonomy, locations, and provider-profiles migration was not found") + } + + contents, err := os.ReadFile(migrationPath) + if err != nil { + t.Fatalf("read migration: %v", err) + } + migration := strings.ToLower(string(contents)) + + for _, requirement := range []string{ + "create extension if not exists postgis", + "create table public.supported_locales", + "create table public.service_categories", + "create table public.service_category_translations", + "primary key (category_id, locale)", + "create table public.spoken_languages", + "create table public.spoken_language_translations", + "primary key (language_code, locale)", + "create table public.administrative_areas", + "create table public.localities", + "center geography(point, 4326) generated always as", + "st_setsrid(st_makepoint(longitude, latitude), 4326)::geography", + "create table public.provider_profiles", + "internal_user_id uuid primary key", + "references public.user_accounts(internal_user_id) on delete cascade", + "provider_type in ('individual', 'professional', 'business')", + "max_travel_distance_km between 0 and 200", + "travels_to_customer or receives_customer or remote_services", + "create table public.provider_service_localities", + "primary key (internal_user_id, locality_id)", + "create table public.provider_spoken_languages", + "primary key (internal_user_id, language_code)", + "'050205'", + "'050510'", + "'050518'", + "'050520'", + "'050521'", + "'r5396187'", + "'r5395738'", + "'n371426674'", + "'r5431477'", + "'n440173641'", + "'home-repairs'", + "'computer-repair'", + } { + if !strings.Contains(migration, requirement) { + t.Errorf("migration does not contain %q", requirement) + } + } + + for _, prohibited := range []string{ + "clerk_subject", + "email", + "phone", + "whatsapp", + "exact_address", + "payment", + "identity_document", + } { + if strings.Contains(migration, prohibited) { + t.Errorf("migration must not include %q", prohibited) + } + } +} diff --git a/supabase/migrations/20260823165327_create_taxonomy_locations_provider_profiles.sql b/supabase/migrations/20260823165327_create_taxonomy_locations_provider_profiles.sql new file mode 100644 index 0000000..0fb5540 --- /dev/null +++ b/supabase/migrations/20260823165327_create_taxonomy_locations_provider_profiles.sql @@ -0,0 +1,225 @@ +create extension if not exists postgis; + +create table public.supported_locales ( + id text primary key, + active boolean not null default true, + sort_order integer not null, + constraint supported_locales_id_length check (char_length(id) between 2 and 10), + constraint supported_locales_sort_order_nonnegative check (sort_order >= 0) +); + +create table public.service_categories ( + id uuid primary key, + parent_id uuid references public.service_categories(id) on delete restrict, + slug text not null unique, + active boolean not null default true, + sort_order integer not null, + created_at timestamptz not null default timezone('utc', now()), + updated_at timestamptz not null default timezone('utc', now()), + constraint service_categories_parent_not_self check (parent_id is null or parent_id <> id), + constraint service_categories_slug_length check (char_length(slug) between 1 and 80), + constraint service_categories_sort_order_nonnegative check (sort_order >= 0) +); + +create index service_categories_parent_sort_idx + on public.service_categories (parent_id, sort_order, id); + +create table public.service_category_translations ( + category_id uuid not null references public.service_categories(id) on delete cascade, + locale text not null references public.supported_locales(id) on delete restrict, + name text not null, + description text, + primary key (category_id, locale), + constraint service_category_translation_name_length check (char_length(name) between 1 and 120), + constraint service_category_translation_description_length check (description is null or char_length(description) <= 500) +); + +create table public.spoken_languages ( + id text primary key, + active boolean not null default true, + sort_order integer not null, + constraint spoken_languages_id_length check (char_length(id) between 2 and 10), + constraint spoken_languages_sort_order_nonnegative check (sort_order >= 0) +); + +create table public.spoken_language_translations ( + language_code text not null references public.spoken_languages(id) on delete cascade, + locale text not null references public.supported_locales(id) on delete restrict, + name text not null, + primary key (language_code, locale), + constraint spoken_language_translation_name_length check (char_length(name) between 1 and 80) +); + +create table public.administrative_areas ( + id uuid primary key, + source text not null, + source_version text not null, + external_code text not null, + kind text not null, + name text not null, + parent_id uuid references public.administrative_areas(id) on delete restrict, + active boolean not null default true, + created_at timestamptz not null default timezone('utc', now()), + updated_at timestamptz not null default timezone('utc', now()), + constraint administrative_areas_source_length check (char_length(source) between 1 and 40), + constraint administrative_areas_source_version_length check (char_length(source_version) between 1 and 20), + constraint administrative_areas_external_code_length check (char_length(external_code) between 1 and 32), + constraint administrative_areas_kind check (kind in ('country', 'district', 'municipality', 'parish')), + constraint administrative_areas_name_length check (char_length(name) between 1 and 160), + constraint administrative_areas_parent_not_self check (parent_id is null or parent_id <> id), + unique (source, external_code) +); + +create index administrative_areas_parent_kind_idx + on public.administrative_areas (parent_id, kind, id); + +create table public.localities ( + id uuid primary key, + slug text not null unique, + name text not null, + parent_parish_id uuid not null references public.administrative_areas(id) on delete restrict, + source text not null, + source_element_id text not null unique, + source_version text not null, + source_retrieved_at timestamptz not null, + latitude double precision not null, + longitude double precision not null, + center geography(point, 4326) generated always as + (st_setsrid(st_makepoint(longitude, latitude), 4326)::geography) stored, + active boolean not null default true, + created_at timestamptz not null default timezone('utc', now()), + updated_at timestamptz not null default timezone('utc', now()), + constraint localities_slug_length check (char_length(slug) between 1 and 100), + constraint localities_name_length check (char_length(name) between 1 and 160), + constraint localities_source_length check (char_length(source) between 1 and 40), + constraint localities_source_element_id_length check (char_length(source_element_id) between 2 and 32), + constraint localities_source_version_length check (char_length(source_version) between 1 and 20), + constraint localities_latitude_range check (latitude between -90 and 90), + constraint localities_longitude_range check (longitude between -180 and 180) +); + +create index localities_parent_name_idx + on public.localities (parent_parish_id, name, id); +create index localities_center_gist_idx + on public.localities using gist (center); + +create table public.provider_profiles ( + internal_user_id uuid primary key references public.user_accounts(internal_user_id) on delete cascade, + display_name text not null, + provider_type text not null, + bio text not null, + primary_locality_id uuid not null references public.localities(id) on delete restrict, + max_travel_distance_km integer not null, + travels_to_customer boolean not null default false, + receives_customer boolean not null default false, + remote_services boolean not null default false, + created_at timestamptz not null default timezone('utc', now()), + updated_at timestamptz not null default timezone('utc', now()), + constraint provider_profiles_display_name_length check (char_length(btrim(display_name)) between 2 and 100), + constraint provider_profiles_type check (provider_type in ('individual', 'professional', 'business')), + constraint provider_profiles_bio_length check (char_length(bio) <= 1000), + constraint provider_profiles_travel_distance check (max_travel_distance_km between 0 and 200), + constraint provider_profiles_service_mode check (travels_to_customer or receives_customer or remote_services), + constraint provider_profiles_zero_travel_mode check (max_travel_distance_km > 0 or receives_customer or remote_services) +); + +create index provider_profiles_primary_locality_idx + on public.provider_profiles (primary_locality_id, internal_user_id); + +create table public.provider_service_localities ( + internal_user_id uuid not null references public.provider_profiles(internal_user_id) on delete cascade, + locality_id uuid not null references public.localities(id) on delete restrict, + primary key (internal_user_id, locality_id) +); + +create index provider_service_localities_locality_idx + on public.provider_service_localities (locality_id, internal_user_id); + +create table public.provider_spoken_languages ( + internal_user_id uuid not null references public.provider_profiles(internal_user_id) on delete cascade, + language_code text not null references public.spoken_languages(id) on delete restrict, + primary key (internal_user_id, language_code) +); + +create index provider_spoken_languages_language_idx + on public.provider_spoken_languages (language_code, internal_user_id); + +insert into public.supported_locales (id, active, sort_order) values + ('pt-PT', true, 10), + ('en', true, 20), + ('es', true, 30); + +insert into public.service_categories (id, parent_id, slug, active, sort_order) values + (md5('juntly:category:home-repairs')::uuid, null, 'home-repairs', true, 10), + (md5('juntly:category:plumbing')::uuid, md5('juntly:category:home-repairs')::uuid, 'plumbing', true, 10), + (md5('juntly:category:electrical-work')::uuid, md5('juntly:category:home-repairs')::uuid, 'electrical-work', true, 20), + (md5('juntly:category:construction')::uuid, md5('juntly:category:home-repairs')::uuid, 'construction', true, 30), + (md5('juntly:category:small-repairs')::uuid, md5('juntly:category:home-repairs')::uuid, 'small-repairs', true, 40), + (md5('juntly:category:home-and-garden')::uuid, null, 'home-and-garden', true, 20), + (md5('juntly:category:cleaning')::uuid, md5('juntly:category:home-and-garden')::uuid, 'cleaning', true, 10), + (md5('juntly:category:gardening')::uuid, md5('juntly:category:home-and-garden')::uuid, 'gardening', true, 20), + (md5('juntly:category:rural-and-transport')::uuid, null, 'rural-and-transport', true, 30), + (md5('juntly:category:agricultural-assistance')::uuid, md5('juntly:category:rural-and-transport')::uuid, 'agricultural-assistance', true, 10), + (md5('juntly:category:transport')::uuid, md5('juntly:category:rural-and-transport')::uuid, 'transport', true, 20), + (md5('juntly:category:care-and-learning')::uuid, null, 'care-and-learning', true, 40), + (md5('juntly:category:elderly-assistance')::uuid, md5('juntly:category:care-and-learning')::uuid, 'elderly-assistance', true, 10), + (md5('juntly:category:animal-care')::uuid, md5('juntly:category:care-and-learning')::uuid, 'animal-care', true, 20), + (md5('juntly:category:private-lessons')::uuid, md5('juntly:category:care-and-learning')::uuid, 'private-lessons', true, 30), + (md5('juntly:category:food-and-technology')::uuid, null, 'food-and-technology', true, 50), + (md5('juntly:category:meal-preparation')::uuid, md5('juntly:category:food-and-technology')::uuid, 'meal-preparation', true, 10), + (md5('juntly:category:computer-repair')::uuid, md5('juntly:category:food-and-technology')::uuid, 'computer-repair', true, 20); + +with category_names(slug, locale, name) as (values + ('home-repairs', 'pt-PT', 'Reparações domésticas'), ('home-repairs', 'en', 'Home repairs'), ('home-repairs', 'es', 'Reparaciones del hogar'), + ('plumbing', 'pt-PT', 'Canalização'), ('plumbing', 'en', 'Plumbing'), ('plumbing', 'es', 'Fontanería'), + ('electrical-work', 'pt-PT', 'Eletricidade'), ('electrical-work', 'en', 'Electrical work'), ('electrical-work', 'es', 'Electricidad'), + ('construction', 'pt-PT', 'Construção'), ('construction', 'en', 'Construction'), ('construction', 'es', 'Construcción'), + ('small-repairs', 'pt-PT', 'Pequenas reparações'), ('small-repairs', 'en', 'Small repairs'), ('small-repairs', 'es', 'Pequeñas reparaciones'), + ('home-and-garden', 'pt-PT', 'Casa e jardim'), ('home-and-garden', 'en', 'Home and garden'), ('home-and-garden', 'es', 'Hogar y jardín'), + ('cleaning', 'pt-PT', 'Limpeza'), ('cleaning', 'en', 'Cleaning'), ('cleaning', 'es', 'Limpieza'), + ('gardening', 'pt-PT', 'Jardinagem'), ('gardening', 'en', 'Gardening'), ('gardening', 'es', 'Jardinería'), + ('rural-and-transport', 'pt-PT', 'Serviços rurais e transporte'), ('rural-and-transport', 'en', 'Rural services and transport'), ('rural-and-transport', 'es', 'Servicios rurales y transporte'), + ('agricultural-assistance', 'pt-PT', 'Apoio agrícola'), ('agricultural-assistance', 'en', 'Agricultural assistance'), ('agricultural-assistance', 'es', 'Ayuda agrícola'), + ('transport', 'pt-PT', 'Transporte'), ('transport', 'en', 'Transport'), ('transport', 'es', 'Transporte'), + ('care-and-learning', 'pt-PT', 'Cuidados e aprendizagem'), ('care-and-learning', 'en', 'Care and learning'), ('care-and-learning', 'es', 'Cuidados y aprendizaje'), + ('elderly-assistance', 'pt-PT', 'Apoio a idosos'), ('elderly-assistance', 'en', 'Elderly assistance'), ('elderly-assistance', 'es', 'Ayuda a mayores'), + ('animal-care', 'pt-PT', 'Cuidados de animais'), ('animal-care', 'en', 'Animal care'), ('animal-care', 'es', 'Cuidado de animales'), + ('private-lessons', 'pt-PT', 'Aulas particulares'), ('private-lessons', 'en', 'Private lessons'), ('private-lessons', 'es', 'Clases particulares'), + ('food-and-technology', 'pt-PT', 'Alimentação e tecnologia'), ('food-and-technology', 'en', 'Food and technology'), ('food-and-technology', 'es', 'Alimentación y tecnología'), + ('meal-preparation', 'pt-PT', 'Preparação de refeições'), ('meal-preparation', 'en', 'Meal preparation'), ('meal-preparation', 'es', 'Preparación de comidas'), + ('computer-repair', 'pt-PT', 'Reparação de computadores'), ('computer-repair', 'en', 'Computer repair'), ('computer-repair', 'es', 'Reparación de ordenadores') +) +insert into public.service_category_translations (category_id, locale, name) +select categories.id, category_names.locale, category_names.name +from category_names +join public.service_categories categories on categories.slug = category_names.slug; + +insert into public.spoken_languages (id, active, sort_order) values + ('pt-PT', true, 10), + ('en', true, 20), + ('es', true, 30); + +insert into public.spoken_language_translations (language_code, locale, name) values + ('pt-PT', 'pt-PT', 'Português'), ('pt-PT', 'en', 'Portuguese'), ('pt-PT', 'es', 'Portugués'), + ('en', 'pt-PT', 'Inglês'), ('en', 'en', 'English'), ('en', 'es', 'Inglés'), + ('es', 'pt-PT', 'Espanhol'), ('es', 'en', 'Spanish'), ('es', 'es', 'Español'); + +insert into public.administrative_areas + (id, source, source_version, external_code, kind, name, parent_id, active) values + (md5('juntly:area:caop:PT')::uuid, 'caop', '2025', 'PT', 'country', 'Portugal', null, true), + (md5('juntly:area:caop:05')::uuid, 'caop', '2025', '05', 'district', 'Castelo Branco', md5('juntly:area:caop:PT')::uuid, true), + (md5('juntly:area:caop:0502')::uuid, 'caop', '2025', '0502', 'municipality', 'Castelo Branco', md5('juntly:area:caop:05')::uuid, true), + (md5('juntly:area:caop:0505')::uuid, 'caop', '2025', '0505', 'municipality', 'Idanha-a-Nova', md5('juntly:area:caop:05')::uuid, true), + (md5('juntly:area:caop:050205')::uuid, 'caop', '2025', '050205', 'parish', 'Castelo Branco', md5('juntly:area:caop:0502')::uuid, true), + (md5('juntly:area:caop:050510')::uuid, 'caop', '2025', '050510', 'parish', 'Penha Garcia', md5('juntly:area:caop:0505')::uuid, true), + (md5('juntly:area:caop:050518')::uuid, 'caop', '2025', '050518', 'parish', 'União das freguesias de Idanha-a-Nova e Alcafozes', md5('juntly:area:caop:0505')::uuid, true), + (md5('juntly:area:caop:050520')::uuid, 'caop', '2025', '050520', 'parish', 'União das freguesias de Monsanto e Idanha-a-Velha', md5('juntly:area:caop:0505')::uuid, true), + (md5('juntly:area:caop:050521')::uuid, 'caop', '2025', '050521', 'parish', 'União das freguesias de Zebreira e Segura', md5('juntly:area:caop:0505')::uuid, true); + +insert into public.localities + (id, slug, name, parent_parish_id, source, source_element_id, source_version, source_retrieved_at, latitude, longitude, active) values + (md5('juntly:locality:castelo-branco')::uuid, 'castelo-branco', 'Castelo Branco', md5('juntly:area:caop:050205')::uuid, 'OpenStreetMap', 'R5396187', '2026-08-23', '2026-08-23T00:00:00Z', 39.8266322, -7.4919318, true), + (md5('juntly:locality:idanha-a-nova')::uuid, 'idanha-a-nova', 'Idanha-a-Nova', md5('juntly:area:caop:050518')::uuid, 'OpenStreetMap', 'R5395738', '2026-08-23', '2026-08-23T00:00:00Z', 39.9260883, -7.2436356, true), + (md5('juntly:locality:monsanto')::uuid, 'monsanto', 'Monsanto', md5('juntly:area:caop:050520')::uuid, 'OpenStreetMap', 'N371426674', '2026-08-23', '2026-08-23T00:00:00Z', 40.0387510, -7.1151133, true), + (md5('juntly:locality:penha-garcia')::uuid, 'penha-garcia', 'Penha Garcia', md5('juntly:area:caop:050510')::uuid, 'OpenStreetMap', 'R5431477', '2026-08-23', '2026-08-23T00:00:00Z', 40.0422569, -7.0163521, true), + (md5('juntly:locality:zebreira')::uuid, 'zebreira', 'Zebreira', md5('juntly:area:caop:050521')::uuid, 'OpenStreetMap', 'N440173641', '2026-08-23', '2026-08-23T00:00:00Z', 39.8455920, -7.0703366, true); From 89387382762484260e24944417b3c2e0384b99bb Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 17:08:30 +0100 Subject: [PATCH 16/64] feat: add marketplace reference catalog --- backend/internal/reference/model.go | 53 ++++ backend/internal/reference/repository.go | 15 ++ backend/internal/reference/service.go | 87 +++++++ backend/internal/reference/service_test.go | 128 ++++++++++ backend/internal/reference/sql_repository.go | 231 ++++++++++++++++++ .../internal/reference/sql_repository_test.go | 150 ++++++++++++ 6 files changed, 664 insertions(+) create mode 100644 backend/internal/reference/model.go create mode 100644 backend/internal/reference/repository.go create mode 100644 backend/internal/reference/service.go create mode 100644 backend/internal/reference/service_test.go create mode 100644 backend/internal/reference/sql_repository.go create mode 100644 backend/internal/reference/sql_repository_test.go diff --git a/backend/internal/reference/model.go b/backend/internal/reference/model.go new file mode 100644 index 0000000..8bdcf67 --- /dev/null +++ b/backend/internal/reference/model.go @@ -0,0 +1,53 @@ +package reference + +import ( + "errors" + + "github.com/google/uuid" +) + +var ( + ErrInvalidRequest = errors.New("invalid reference request") + ErrInvalidReference = errors.New("invalid profile reference") + ErrNotFound = errors.New("reference not found") + ErrUnavailable = errors.New("reference data unavailable") +) + +const ( + AttributionText = "© OpenStreetMap contributors" + AttributionURL = "https://www.openstreetmap.org/copyright" +) + +type Category struct { + ID uuid.UUID + ParentID *uuid.UUID + Slug string + Name string + SortOrder int +} + +type Language struct { + Code string + Name string + SortOrder int +} + +type Locality struct { + ID uuid.UUID + Slug string + Name string + ParishName string + MunicipalityName string + DistrictName string +} + +type LocalityDistance struct { + Locality + DistanceMeters int +} + +type ProfileReferences struct { + PrimaryLocalityID uuid.UUID + ServiceLocalityIDs []uuid.UUID + LanguageCodes []string +} diff --git a/backend/internal/reference/repository.go b/backend/internal/reference/repository.go new file mode 100644 index 0000000..ff44a6d --- /dev/null +++ b/backend/internal/reference/repository.go @@ -0,0 +1,15 @@ +package reference + +import ( + "context" + + "github.com/google/uuid" +) + +type Repository interface { + Categories(context.Context, string) ([]Category, error) + Languages(context.Context, string) ([]Language, error) + Localities(context.Context, string) ([]Locality, error) + NearbyLocalities(context.Context, uuid.UUID, int, string) ([]LocalityDistance, error) + ValidateProfileReferences(context.Context, ProfileReferences) error +} diff --git a/backend/internal/reference/service.go b/backend/internal/reference/service.go new file mode 100644 index 0000000..e54930c --- /dev/null +++ b/backend/internal/reference/service.go @@ -0,0 +1,87 @@ +package reference + +import ( + "context" + "errors" + + "github.com/google/uuid" +) + +type Service interface { + Categories(context.Context, string) ([]Category, error) + Languages(context.Context, string) ([]Language, error) + Localities(context.Context, string) ([]Locality, error) + NearbyLocalities(context.Context, uuid.UUID, int, string) ([]LocalityDistance, error) +} + +type service struct{ repository Repository } + +func NewService(repository Repository) Service { + return service{repository: repository} +} + +func (s service) Categories(ctx context.Context, locale string) ([]Category, error) { + if !validLocale(locale) { + return nil, ErrInvalidRequest + } + if s.repository == nil { + return nil, ErrUnavailable + } + values, err := s.repository.Categories(ctx, locale) + return values, publicRepositoryError(err) +} + +func (s service) Languages(ctx context.Context, locale string) ([]Language, error) { + if !validLocale(locale) { + return nil, ErrInvalidRequest + } + if s.repository == nil { + return nil, ErrUnavailable + } + values, err := s.repository.Languages(ctx, locale) + return values, publicRepositoryError(err) +} + +func (s service) Localities(ctx context.Context, locale string) ([]Locality, error) { + if !validLocale(locale) { + return nil, ErrInvalidRequest + } + if s.repository == nil { + return nil, ErrUnavailable + } + values, err := s.repository.Localities(ctx, locale) + return values, publicRepositoryError(err) +} + +func (s service) NearbyLocalities(ctx context.Context, origin uuid.UUID, radiusKM int, locale string) ([]LocalityDistance, error) { + if origin == uuid.Nil || radiusKM < 1 || radiusKM > 200 || !validLocale(locale) { + return nil, ErrInvalidRequest + } + if s.repository == nil { + return nil, ErrUnavailable + } + values, err := s.repository.NearbyLocalities(ctx, origin, radiusKM, locale) + return values, publicRepositoryError(err) +} + +func validLocale(locale string) bool { + switch locale { + case "pt-PT", "en", "es": + return true + default: + return false + } +} + +func publicRepositoryError(err error) error { + switch { + case err == nil: + return nil + case errors.Is(err, ErrNotFound): + return ErrNotFound + case errors.Is(err, ErrInvalidReference): + return ErrInvalidReference + default: + return ErrUnavailable + } +} diff --git a/backend/internal/reference/service_test.go b/backend/internal/reference/service_test.go new file mode 100644 index 0000000..42618f4 --- /dev/null +++ b/backend/internal/reference/service_test.go @@ -0,0 +1,128 @@ +package reference + +import ( + "context" + "errors" + "testing" + + "github.com/google/uuid" +) + +func TestServiceRejectsUnsupportedLocaleBeforeRepositoryAccess(t *testing.T) { + t.Parallel() + + repository := &recordingRepository{} + service := NewService(repository) + for _, locale := range []string{"", "fr", "pt", "pt-PT-extra"} { + if _, err := service.Categories(context.Background(), locale); !errors.Is(err, ErrInvalidRequest) { + t.Fatalf("locale %q error = %v, want ErrInvalidRequest", locale, err) + } + } + if repository.calls != 0 { + t.Fatalf("repository calls = %d, want 0", repository.calls) + } +} + +func TestServiceReturnsReferenceCatalogs(t *testing.T) { + t.Parallel() + + category := Category{ID: uuid.New(), Slug: "cleaning", Name: "Limpeza", SortOrder: 10} + language := Language{Code: "pt-PT", Name: "Português", SortOrder: 10} + locality := Locality{ID: uuid.New(), Slug: "zebreira", Name: "Zebreira", ParishName: "Zebreira e Segura", MunicipalityName: "Idanha-a-Nova", DistrictName: "Castelo Branco"} + repository := &recordingRepository{ + categories: []Category{category}, + languages: []Language{language}, + localities: []Locality{locality}, + } + service := NewService(repository) + ctx := context.Background() + + categories, err := service.Categories(ctx, "pt-PT") + if err != nil || len(categories) != 1 || categories[0] != category { + t.Fatalf("categories = %#v, err = %v", categories, err) + } + languages, err := service.Languages(ctx, "pt-PT") + if err != nil || len(languages) != 1 || languages[0] != language { + t.Fatalf("languages = %#v, err = %v", languages, err) + } + localities, err := service.Localities(ctx, "pt-PT") + if err != nil || len(localities) != 1 || localities[0] != locality { + t.Fatalf("localities = %#v, err = %v", localities, err) + } +} + +func TestServiceValidatesNearbyLocalityRequest(t *testing.T) { + t.Parallel() + + repository := &recordingRepository{} + service := NewService(repository) + ctx := context.Background() + validID := uuid.New() + + for _, test := range []struct { + origin uuid.UUID + radius int + locale string + }{ + {uuid.Nil, 10, "pt-PT"}, + {validID, 0, "pt-PT"}, + {validID, 201, "pt-PT"}, + {validID, 10, "fr"}, + } { + if _, err := service.NearbyLocalities(ctx, test.origin, test.radius, test.locale); !errors.Is(err, ErrInvalidRequest) { + t.Fatalf("request %#v error = %v, want ErrInvalidRequest", test, err) + } + } + if repository.calls != 0 { + t.Fatalf("repository calls = %d, want 0", repository.calls) + } +} + +func TestServiceMapsRepositoryFailures(t *testing.T) { + t.Parallel() + + repository := &recordingRepository{err: errors.New("database internal details")} + service := NewService(repository) + ctx := context.Background() + + if _, err := service.Categories(ctx, "en"); !errors.Is(err, ErrUnavailable) { + t.Fatalf("categories error = %v, want ErrUnavailable", err) + } + if _, err := service.NearbyLocalities(ctx, uuid.New(), 10, "es"); !errors.Is(err, ErrUnavailable) { + t.Fatalf("nearby error = %v, want ErrUnavailable", err) + } +} + +type recordingRepository struct { + categories []Category + languages []Language + localities []Locality + nearby []LocalityDistance + err error + calls int +} + +func (r *recordingRepository) Categories(context.Context, string) ([]Category, error) { + r.calls++ + return r.categories, r.err +} + +func (r *recordingRepository) Languages(context.Context, string) ([]Language, error) { + r.calls++ + return r.languages, r.err +} + +func (r *recordingRepository) Localities(context.Context, string) ([]Locality, error) { + r.calls++ + return r.localities, r.err +} + +func (r *recordingRepository) NearbyLocalities(context.Context, uuid.UUID, int, string) ([]LocalityDistance, error) { + r.calls++ + return r.nearby, r.err +} + +func (r *recordingRepository) ValidateProfileReferences(context.Context, ProfileReferences) error { + r.calls++ + return r.err +} diff --git a/backend/internal/reference/sql_repository.go b/backend/internal/reference/sql_repository.go new file mode 100644 index 0000000..20c557f --- /dev/null +++ b/backend/internal/reference/sql_repository.go @@ -0,0 +1,231 @@ +package reference + +import ( + "context" + "database/sql" + "errors" + + "github.com/google/uuid" +) + +type sqlRepository struct{ database *sql.DB } + +func NewSQLRepository(database *sql.DB) Repository { + return sqlRepository{database: database} +} + +func (r sqlRepository) Categories(ctx context.Context, locale string) ([]Category, error) { + if r.database == nil { + return nil, errors.New("database is nil") + } + rows, err := r.database.QueryContext(ctx, ` + select category.id, category.parent_id, category.slug, translation.name, category.sort_order + from public.service_categories category + join public.service_category_translations translation + on translation.category_id = category.id and translation.locale = $1 + join public.supported_locales locale + on locale.id = translation.locale and locale.active + left join public.service_categories parent on parent.id = category.parent_id + where category.active and (category.parent_id is null or parent.active) + order by coalesce(parent.sort_order, category.sort_order), + case when category.parent_id is null then 0 else 1 end, + category.sort_order, + category.id + `, locale) + if err != nil { + return nil, err + } + defer rows.Close() + + categories := make([]Category, 0) + for rows.Next() { + var category Category + if err := rows.Scan(&category.ID, &category.ParentID, &category.Slug, &category.Name, &category.SortOrder); err != nil { + return nil, err + } + categories = append(categories, category) + } + if err := rows.Err(); err != nil { + return nil, err + } + return categories, nil +} + +func (r sqlRepository) Languages(ctx context.Context, locale string) ([]Language, error) { + if r.database == nil { + return nil, errors.New("database is nil") + } + rows, err := r.database.QueryContext(ctx, ` + select language.id, translation.name, language.sort_order + from public.spoken_languages language + join public.spoken_language_translations translation + on translation.language_code = language.id and translation.locale = $1 + join public.supported_locales locale + on locale.id = translation.locale and locale.active + where language.active + order by language.sort_order, language.id + `, locale) + if err != nil { + return nil, err + } + defer rows.Close() + + languages := make([]Language, 0) + for rows.Next() { + var language Language + if err := rows.Scan(&language.Code, &language.Name, &language.SortOrder); err != nil { + return nil, err + } + languages = append(languages, language) + } + if err := rows.Err(); err != nil { + return nil, err + } + return languages, nil +} + +func (r sqlRepository) Localities(ctx context.Context, locale string) ([]Locality, error) { + if r.database == nil { + return nil, errors.New("database is nil") + } + rows, err := r.database.QueryContext(ctx, localitySelectSQL+` + cross join public.supported_locales locale + where locality.active and parish.active and municipality.active and district.active + and locale.id = $1 and locale.active + order by locality.name, locality.id + `, locale) + if err != nil { + return nil, err + } + defer rows.Close() + return scanLocalities(rows) +} + +func (r sqlRepository) NearbyLocalities(ctx context.Context, origin uuid.UUID, radiusKM int, locale string) ([]LocalityDistance, error) { + if r.database == nil { + return nil, errors.New("database is nil") + } + rows, err := r.database.QueryContext(ctx, ` + with origin as ( + select center from public.localities where id = $1 and active + ) + select locality.id, locality.slug, locality.name, + parish.name, municipality.name, district.name, + round(st_distance(locality.center, origin.center))::integer as distance_meters + from origin + cross join public.localities locality + join public.administrative_areas parish on parish.id = locality.parent_parish_id + join public.administrative_areas municipality on municipality.id = parish.parent_id + join public.administrative_areas district on district.id = municipality.parent_id + cross join public.supported_locales locale + where locality.active and parish.active and municipality.active and district.active + and locale.id = $3 and locale.active + and st_dwithin(locality.center, origin.center, $2 * 1000) + order by distance_meters, locality.id + `, origin, radiusKM, locale) + if err != nil { + return nil, err + } + defer rows.Close() + + values := make([]LocalityDistance, 0) + for rows.Next() { + var value LocalityDistance + if err := rows.Scan( + &value.ID, + &value.Slug, + &value.Name, + &value.ParishName, + &value.MunicipalityName, + &value.DistrictName, + &value.DistanceMeters, + ); err != nil { + return nil, err + } + values = append(values, value) + } + if err := rows.Err(); err != nil { + return nil, err + } + if len(values) == 0 { + return nil, ErrNotFound + } + return values, nil +} + +func (r sqlRepository) ValidateProfileReferences(ctx context.Context, references ProfileReferences) error { + if r.database == nil { + return errors.New("database is nil") + } + localityIDs := append([]uuid.UUID{references.PrimaryLocalityID}, references.ServiceLocalityIDs...) + seenLocalities := make(map[uuid.UUID]struct{}, len(localityIDs)) + for _, id := range localityIDs { + if id == uuid.Nil { + return ErrInvalidReference + } + if _, ok := seenLocalities[id]; ok { + continue + } + seenLocalities[id] = struct{}{} + var active bool + if err := r.database.QueryRowContext(ctx, "select active from public.localities where id = $1", id).Scan(&active); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return ErrInvalidReference + } + return err + } + if !active { + return ErrInvalidReference + } + } + + seenLanguages := make(map[string]struct{}, len(references.LanguageCodes)) + for _, code := range references.LanguageCodes { + if _, ok := seenLanguages[code]; ok { + continue + } + seenLanguages[code] = struct{}{} + var active bool + if err := r.database.QueryRowContext(ctx, "select active from public.spoken_languages where id = $1", code).Scan(&active); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return ErrInvalidReference + } + return err + } + if !active { + return ErrInvalidReference + } + } + return nil +} + +const localitySelectSQL = ` + select locality.id, locality.slug, locality.name, + parish.name, municipality.name, district.name + from public.localities locality + join public.administrative_areas parish on parish.id = locality.parent_parish_id + join public.administrative_areas municipality on municipality.id = parish.parent_id + join public.administrative_areas district on district.id = municipality.parent_id +` + +func scanLocalities(rows *sql.Rows) ([]Locality, error) { + localities := make([]Locality, 0) + for rows.Next() { + var locality Locality + if err := rows.Scan( + &locality.ID, + &locality.Slug, + &locality.Name, + &locality.ParishName, + &locality.MunicipalityName, + &locality.DistrictName, + ); err != nil { + return nil, err + } + localities = append(localities, locality) + } + if err := rows.Err(); err != nil { + return nil, err + } + return localities, nil +} diff --git a/backend/internal/reference/sql_repository_test.go b/backend/internal/reference/sql_repository_test.go new file mode 100644 index 0000000..b286fdc --- /dev/null +++ b/backend/internal/reference/sql_repository_test.go @@ -0,0 +1,150 @@ +package reference + +import ( + "context" + "database/sql" + "os" + "testing" + + "github.com/google/uuid" + _ "github.com/jackc/pgx/v5/stdlib" +) + +func TestSQLRepositoryReturnsCompleteLocalizedReferenceCatalog(t *testing.T) { + database := openReferenceDatabase(t) + repository := NewSQLRepository(database) + ctx := context.Background() + + categories, err := repository.Categories(ctx, "pt-PT") + if err != nil { + t.Fatalf("categories: %v", err) + } + if len(categories) != 18 { + t.Fatalf("category count = %d, want 18", len(categories)) + } + if categories[0].Slug != "home-repairs" || categories[0].ParentID != nil || categories[0].Name != "Reparações domésticas" { + t.Fatalf("first category = %#v", categories[0]) + } + seen := make(map[uuid.UUID]struct{}, len(categories)) + for _, category := range categories { + if _, exists := seen[category.ID]; exists { + t.Fatalf("duplicate category ID %s", category.ID) + } + seen[category.ID] = struct{}{} + } + + languages, err := repository.Languages(ctx, "en") + if err != nil { + t.Fatalf("languages: %v", err) + } + if len(languages) != 3 || languages[0].Code != "pt-PT" || languages[0].Name != "Portuguese" { + t.Fatalf("languages = %#v", languages) + } + + localities, err := repository.Localities(ctx, "es") + if err != nil { + t.Fatalf("localities: %v", err) + } + if len(localities) != 5 { + t.Fatalf("locality count = %d, want 5", len(localities)) + } + for _, locality := range localities { + if locality.ID == uuid.Nil || locality.Name == "" || locality.ParishName == "" || locality.MunicipalityName == "" || locality.DistrictName == "" { + t.Fatalf("incomplete locality = %#v", locality) + } + } +} + +func TestSQLRepositoryRadiusOrderingAndActiveFiltering(t *testing.T) { + database := openReferenceDatabase(t) + ctx := context.Background() + parentID := seededParishID(t, database, "050205") + ids := []uuid.UUID{ + uuid.MustParse("00000000-0000-4000-8000-000000000010"), + uuid.MustParse("00000000-0000-4000-8000-000000000011"), + uuid.MustParse("00000000-0000-4000-8000-000000000012"), + uuid.MustParse("00000000-0000-4000-8000-000000000013"), + uuid.MustParse("00000000-0000-4000-8000-000000000014"), + } + rows := []struct { + id uuid.UUID + slug string + latitude float64 + longitude float64 + active bool + }{ + {ids[0], "test-radius-origin", 39.8, -7.0, true}, + {ids[1], "test-radius-near-a", 39.8, -7.01, true}, + {ids[2], "test-radius-near-b", 39.8, -6.99, true}, + {ids[3], "test-radius-inactive", 39.8, -7.005, false}, + {ids[4], "test-radius-out", 39.8, -7.1, true}, + } + for _, row := range rows { + _, err := database.ExecContext(ctx, ` + insert into public.localities + (id, slug, name, parent_parish_id, source, source_element_id, source_version, source_retrieved_at, latitude, longitude, active) + values ($1, $2, $3, $4, 'synthetic-test', $5, '1', '2026-08-23T00:00:00Z', $6, $7, $8) + `, row.id, row.slug, row.slug, parentID, "T:"+row.slug, row.latitude, row.longitude, row.active) + if err != nil { + t.Fatalf("insert locality %s: %v", row.slug, err) + } + } + t.Cleanup(func() { + for _, id := range ids { + if _, err := database.ExecContext(ctx, "delete from public.localities where id = $1", id); err != nil { + t.Errorf("cleanup locality: %v", err) + } + } + }) + + nearby, err := NewSQLRepository(database).NearbyLocalities(ctx, ids[0], 2, "pt-PT") + if err != nil { + t.Fatalf("nearby localities: %v", err) + } + if len(nearby) != 3 { + t.Fatalf("nearby count = %d, want 3 (%#v)", len(nearby), nearby) + } + if nearby[0].ID != ids[0] || nearby[0].DistanceMeters != 0 { + t.Fatalf("origin result = %#v", nearby[0]) + } + if nearby[1].ID != ids[1] || nearby[2].ID != ids[2] { + t.Fatalf("equal-distance ordering = %#v", nearby) + } + for _, result := range nearby { + if result.ID == ids[3] || result.ID == ids[4] { + t.Fatalf("inactive/out-of-range locality included: %#v", result) + } + } + + _, err = NewSQLRepository(database).NearbyLocalities(ctx, uuid.New(), 2, "pt-PT") + if err != ErrNotFound { + t.Fatalf("missing origin error = %v, want ErrNotFound", err) + } +} + +func openReferenceDatabase(t *testing.T) *sql.DB { + t.Helper() + databaseURL := os.Getenv("TEST_DATABASE_URL") + if databaseURL == "" { + t.Skip("TEST_DATABASE_URL is required for PostgreSQL integration tests") + } + database, err := sql.Open("pgx", databaseURL) + if err != nil { + t.Fatalf("open database: %v", err) + } + t.Cleanup(func() { + if err := database.Close(); err != nil { + t.Errorf("close database: %v", err) + } + }) + return database +} + +func seededParishID(t *testing.T, database *sql.DB, code string) uuid.UUID { + t.Helper() + var id uuid.UUID + if err := database.QueryRow("select id from public.administrative_areas where source = 'caop' and external_code = $1", code).Scan(&id); err != nil { + t.Fatalf("load seeded parish: %v", err) + } + return id +} From e1d0e12982fcce3fa91c25a85a82efaefe5c4ab2 Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 17:23:18 +0100 Subject: [PATCH 17/64] feat: add owner-only provider profiles --- backend/internal/provideraccess/service.go | 60 +++++ .../internal/provideraccess/service_test.go | 106 ++++++++ backend/internal/providers/ent_repository.go | 185 +++++++++++++ .../internal/providers/ent_repository_test.go | 252 ++++++++++++++++++ backend/internal/providers/model.go | 49 ++++ backend/internal/providers/repository.go | 12 + backend/internal/providers/service.go | 170 ++++++++++++ backend/internal/providers/service_test.go | 239 +++++++++++++++++ 8 files changed, 1073 insertions(+) create mode 100644 backend/internal/provideraccess/service.go create mode 100644 backend/internal/provideraccess/service_test.go create mode 100644 backend/internal/providers/ent_repository.go create mode 100644 backend/internal/providers/ent_repository_test.go create mode 100644 backend/internal/providers/model.go create mode 100644 backend/internal/providers/repository.go create mode 100644 backend/internal/providers/service.go create mode 100644 backend/internal/providers/service_test.go diff --git a/backend/internal/provideraccess/service.go b/backend/internal/provideraccess/service.go new file mode 100644 index 0000000..647d269 --- /dev/null +++ b/backend/internal/provideraccess/service.go @@ -0,0 +1,60 @@ +package provideraccess + +import ( + "context" + "errors" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/accounts" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" +) + +var ( + ErrUnauthorized = errors.New("provider access unauthorized") + ErrForbidden = errors.New("provider access forbidden") + ErrUnavailable = errors.New("provider access unavailable") +) + +type InternalUserReconciler interface { + Reconcile(context.Context, users.VerifiedIdentity) (users.InternalUser, bool, error) +} + +type CapabilityReader interface { + Get(context.Context, users.VerifiedIdentity) (accounts.Account, error) +} + +type Service interface { + RequireProvider(context.Context, users.VerifiedIdentity) (users.InternalUser, error) +} + +type service struct { + identities InternalUserReconciler + capabilities CapabilityReader +} + +func NewService(identities InternalUserReconciler, capabilities CapabilityReader) Service { + return service{identities: identities, capabilities: capabilities} +} + +func (s service) RequireProvider(ctx context.Context, identity users.VerifiedIdentity) (users.InternalUser, error) { + if s.identities == nil || s.capabilities == nil { + return users.InternalUser{}, ErrUnavailable + } + owner, _, err := s.identities.Reconcile(ctx, identity) + if err != nil { + if errors.Is(err, users.ErrInvalidIdentity) { + return users.InternalUser{}, ErrUnauthorized + } + return users.InternalUser{}, ErrUnavailable + } + account, err := s.capabilities.Get(ctx, identity) + if err != nil { + if errors.Is(err, accounts.ErrInvalidIdentity) { + return users.InternalUser{}, ErrUnauthorized + } + return users.InternalUser{}, ErrUnavailable + } + if !account.ProviderEnabled { + return users.InternalUser{}, ErrForbidden + } + return owner, nil +} diff --git a/backend/internal/provideraccess/service_test.go b/backend/internal/provideraccess/service_test.go new file mode 100644 index 0000000..6d6a3ce --- /dev/null +++ b/backend/internal/provideraccess/service_test.go @@ -0,0 +1,106 @@ +package provideraccess + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/accounts" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" + "github.com/google/uuid" +) + +func TestRequireProviderRejectsInvalidIdentity(t *testing.T) { + t.Parallel() + + identities := &recordingIdentityService{err: users.ErrInvalidIdentity} + capabilities := &recordingCapabilityReader{} + _, err := NewService(identities, capabilities).RequireProvider(context.Background(), users.VerifiedIdentity{}) + + if !errors.Is(err, ErrUnauthorized) { + t.Fatalf("error = %v, want ErrUnauthorized", err) + } + if capabilities.calls != 0 { + t.Fatalf("capability calls = %d, want 0", capabilities.calls) + } +} + +func TestRequireProviderRejectsDisabledCapability(t *testing.T) { + t.Parallel() + + identities := &recordingIdentityService{user: testInternalUser()} + capabilities := &recordingCapabilityReader{account: accounts.Account{CustomerEnabled: true}} + _, err := NewService(identities, capabilities).RequireProvider( + context.Background(), + users.VerifiedIdentity{Subject: "user_provider"}, + ) + + if !errors.Is(err, ErrForbidden) { + t.Fatalf("error = %v, want ErrForbidden", err) + } +} + +func TestRequireProviderReturnsVerifiedInternalOwner(t *testing.T) { + t.Parallel() + + owner := testInternalUser() + identities := &recordingIdentityService{user: owner} + capabilities := &recordingCapabilityReader{account: accounts.Account{CustomerEnabled: true, ProviderEnabled: true}} + + result, err := NewService(identities, capabilities).RequireProvider( + context.Background(), + users.VerifiedIdentity{Subject: "user_provider"}, + ) + + if err != nil || result != owner { + t.Fatalf("owner = %#v, err = %v, want %#v", result, err, owner) + } + if identities.calls != 1 || capabilities.calls != 1 { + t.Fatalf("calls = identity:%d capability:%d, want 1:1", identities.calls, capabilities.calls) + } +} + +func TestRequireProviderMapsDependencyFailure(t *testing.T) { + t.Parallel() + + identities := &recordingIdentityService{user: testInternalUser()} + capabilities := &recordingCapabilityReader{err: accounts.ErrUnavailable} + _, err := NewService(identities, capabilities).RequireProvider( + context.Background(), + users.VerifiedIdentity{Subject: "user_provider"}, + ) + + if !errors.Is(err, ErrUnavailable) { + t.Fatalf("error = %v, want ErrUnavailable", err) + } +} + +func testInternalUser() users.InternalUser { + return users.InternalUser{ + ID: uuid.MustParse("b00f5bf7-72a8-4a7d-bb23-2ef4c4daf3fb"), + CreatedAt: time.Date(2026, 8, 23, 16, 0, 0, 0, time.UTC), + } +} + +type recordingIdentityService struct { + user users.InternalUser + err error + calls int +} + +func (s *recordingIdentityService) Reconcile(context.Context, users.VerifiedIdentity) (users.InternalUser, bool, error) { + s.calls++ + return s.user, false, s.err +} + +type recordingCapabilityReader struct { + account accounts.Account + err error + calls int +} + +func (r *recordingCapabilityReader) Get(context.Context, users.VerifiedIdentity) (accounts.Account, error) { + r.calls++ + return r.account, r.err +} diff --git a/backend/internal/providers/ent_repository.go b/backend/internal/providers/ent_repository.go new file mode 100644 index 0000000..1575fd4 --- /dev/null +++ b/backend/internal/providers/ent_repository.go @@ -0,0 +1,185 @@ +package providers + +import ( + "bytes" + "context" + "errors" + "slices" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/ent" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/providerprofile" + "github.com/google/uuid" +) + +type entRepository struct{ client *ent.Client } + +func NewEntRepository(client *ent.Client) Repository { + return entRepository{client: client} +} + +func (r entRepository) FindByOwner(ctx context.Context, owner uuid.UUID) (*Profile, error) { + if r.client == nil { + return nil, errors.New("Ent client is nil") + } + entity, err := queryProviderProfile(ctx, r.client, owner) + if err != nil { + if ent.IsNotFound(err) { + return nil, nil + } + return nil, err + } + profile := profileFromEnt(entity) + return &profile, nil +} + +func (r entRepository) Replace(ctx context.Context, owner uuid.UUID, input ReplaceProfile) (Profile, error) { + if r.client == nil { + return Profile{}, errors.New("Ent client is nil") + } + return r.replace(ctx, owner, canonicalReplacement(input), true) +} + +func (r entRepository) replace(ctx context.Context, owner uuid.UUID, input ReplaceProfile, retryConflict bool) (Profile, error) { + tx, err := r.client.Tx(ctx) + if err != nil { + return Profile{}, err + } + committed := false + defer func() { + if !committed { + _ = tx.Rollback() + } + }() + client := tx.Client() + + existing, err := queryProviderProfile(ctx, client, owner) + switch { + case ent.IsNotFound(err): + _, err = client.ProviderProfile.Create(). + SetID(owner). + SetDisplayName(input.DisplayName). + SetProviderType(string(input.ProviderType)). + SetBio(input.Bio). + SetPrimaryLocalityID(input.PrimaryLocalityID). + SetMaxTravelDistanceKm(input.MaxTravelDistanceKM). + SetTravelsToCustomer(input.TravelsToCustomer). + SetReceivesCustomer(input.ReceivesCustomer). + SetRemoteServices(input.RemoteServices). + AddServiceLocalityIDs(input.ServiceLocalityIDs...). + AddSpokenLanguageIDs(input.LanguageCodes...). + Save(ctx) + if err != nil { + _ = tx.Rollback() + committed = true + if retryConflict && ent.IsConstraintError(err) { + return r.replace(ctx, owner, input, false) + } + return Profile{}, err + } + case err != nil: + return Profile{}, err + case profileMatchesReplacement(profileFromEnt(existing), input): + profile := profileFromEnt(existing) + if err := tx.Commit(); err != nil { + return Profile{}, err + } + committed = true + return profile, nil + default: + _, err = client.ProviderProfile.UpdateOneID(owner). + SetDisplayName(input.DisplayName). + SetProviderType(string(input.ProviderType)). + SetBio(input.Bio). + SetPrimaryLocalityID(input.PrimaryLocalityID). + SetMaxTravelDistanceKm(input.MaxTravelDistanceKM). + SetTravelsToCustomer(input.TravelsToCustomer). + SetReceivesCustomer(input.ReceivesCustomer). + SetRemoteServices(input.RemoteServices). + ClearServiceLocalities(). + AddServiceLocalityIDs(input.ServiceLocalityIDs...). + ClearSpokenLanguages(). + AddSpokenLanguageIDs(input.LanguageCodes...). + Save(ctx) + if err != nil { + return Profile{}, err + } + } + + canonical, err := queryProviderProfile(ctx, client, owner) + if err != nil { + return Profile{}, err + } + profile := profileFromEnt(canonical) + if err := tx.Commit(); err != nil { + return Profile{}, err + } + committed = true + return profile, nil +} + +func queryProviderProfile(ctx context.Context, client *ent.Client, owner uuid.UUID) (*ent.ProviderProfile, error) { + return client.ProviderProfile.Query(). + Where(providerprofile.IDEQ(owner)). + WithServiceLocalities(). + WithSpokenLanguages(). + Only(ctx) +} + +func profileFromEnt(entity *ent.ProviderProfile) Profile { + localityIDs := make([]uuid.UUID, 0, len(entity.Edges.ServiceLocalities)) + for _, locality := range entity.Edges.ServiceLocalities { + localityIDs = append(localityIDs, locality.ID) + } + languageCodes := make([]string, 0, len(entity.Edges.SpokenLanguages)) + for _, language := range entity.Edges.SpokenLanguages { + languageCodes = append(languageCodes, language.ID) + } + sortUUIDs(localityIDs) + slices.Sort(languageCodes) + return Profile{ + DisplayName: entity.DisplayName, + ProviderType: ProviderType(entity.ProviderType), + Bio: entity.Bio, + PrimaryLocalityID: entity.PrimaryLocalityID, + ServiceLocalityIDs: localityIDs, + MaxTravelDistanceKM: entity.MaxTravelDistanceKm, + TravelsToCustomer: entity.TravelsToCustomer, + ReceivesCustomer: entity.ReceivesCustomer, + RemoteServices: entity.RemoteServices, + LanguageCodes: languageCodes, + CreatedAt: normalizeTime(entity.CreatedAt), + UpdatedAt: normalizeTime(entity.UpdatedAt), + } +} + +func canonicalReplacement(input ReplaceProfile) ReplaceProfile { + input.ServiceLocalityIDs = append([]uuid.UUID(nil), input.ServiceLocalityIDs...) + input.LanguageCodes = append([]string(nil), input.LanguageCodes...) + sortUUIDs(input.ServiceLocalityIDs) + slices.Sort(input.LanguageCodes) + return input +} + +func profileMatchesReplacement(profile Profile, input ReplaceProfile) bool { + return profile.DisplayName == input.DisplayName && + profile.ProviderType == input.ProviderType && + profile.Bio == input.Bio && + profile.PrimaryLocalityID == input.PrimaryLocalityID && + profile.MaxTravelDistanceKM == input.MaxTravelDistanceKM && + profile.TravelsToCustomer == input.TravelsToCustomer && + profile.ReceivesCustomer == input.ReceivesCustomer && + profile.RemoteServices == input.RemoteServices && + slices.Equal(profile.ServiceLocalityIDs, input.ServiceLocalityIDs) && + slices.Equal(profile.LanguageCodes, input.LanguageCodes) +} + +func sortUUIDs(values []uuid.UUID) { + slices.SortFunc(values, func(left, right uuid.UUID) int { + return bytes.Compare(left[:], right[:]) + }) +} + +func normalizeTime(value time.Time) time.Time { + return value.UTC().Truncate(time.Microsecond) +} diff --git a/backend/internal/providers/ent_repository_test.go b/backend/internal/providers/ent_repository_test.go new file mode 100644 index 0000000..bb090e8 --- /dev/null +++ b/backend/internal/providers/ent_repository_test.go @@ -0,0 +1,252 @@ +package providers + +import ( + "context" + "database/sql" + "os" + "reflect" + "sync" + "testing" + + "entgo.io/ent/dialect" + entsql "entgo.io/ent/dialect/sql" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/locality" + "github.com/SourceSenseiTheRealOne/juntly/backend/ent/spokenlanguage" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/accounts" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" + "github.com/google/uuid" + _ "github.com/jackc/pgx/v5/stdlib" +) + +func TestEntRepositoryCreatesReadsReplacesAndKeepsIdempotentTimestamps(t *testing.T) { + client := openProviderIntegrationClient(t) + ctx := context.Background() + owner := createProviderOwner(t, client) + localities, languages := seededProfileReferences(t, client) + repository := NewEntRepository(client) + + firstInput := integrationReplacement(localities[:1], languages[:1]) + created, err := repository.Replace(ctx, owner.ID, firstInput) + if err != nil { + t.Fatalf("create profile: %v", err) + } + assertProfileMatchesInput(t, created, firstInput) + + found, err := repository.FindByOwner(ctx, owner.ID) + if err != nil || found == nil { + t.Fatalf("find profile = %#v, err = %v", found, err) + } + assertProfileMatchesInput(t, *found, firstInput) + + secondInput := integrationReplacement(localities[:2], languages[:2]) + secondInput.DisplayName = "Prestador atualizado" + updated, err := repository.Replace(ctx, owner.ID, secondInput) + if err != nil { + t.Fatalf("replace profile: %v", err) + } + assertProfileMatchesInput(t, updated, secondInput) + if updated.CreatedAt != created.CreatedAt { + t.Fatalf("created timestamp changed: got %s want %s", updated.CreatedAt, created.CreatedAt) + } + + repeated, err := repository.Replace(ctx, owner.ID, secondInput) + if err != nil { + t.Fatalf("repeat profile replacement: %v", err) + } + if repeated.UpdatedAt != updated.UpdatedAt { + t.Fatalf("idempotent updated timestamp changed: got %s want %s", repeated.UpdatedAt, updated.UpdatedAt) + } + + missing, err := repository.FindByOwner(ctx, uuid.New()) + if err != nil || missing != nil { + t.Fatalf("cross-owner lookup = %#v, err = %v, want nil nil", missing, err) + } +} + +func TestEntRepositoryRollsBackScalarChangesWhenChildReplacementFails(t *testing.T) { + client := openProviderIntegrationClient(t) + ctx := context.Background() + owner := createProviderOwner(t, client) + localities, languages := seededProfileReferences(t, client) + repository := NewEntRepository(client) + originalInput := integrationReplacement(localities[:1], languages[:1]) + original, err := repository.Replace(ctx, owner.ID, originalInput) + if err != nil { + t.Fatalf("create original profile: %v", err) + } + + invalid := originalInput + invalid.DisplayName = "Must roll back" + invalid.LanguageCodes = []string{"missing-language"} + if _, err := repository.Replace(ctx, owner.ID, invalid); err == nil { + t.Fatal("invalid child replacement error = nil") + } + + found, err := repository.FindByOwner(ctx, owner.ID) + if err != nil || found == nil { + t.Fatalf("find after rollback = %#v, err = %v", found, err) + } + assertProfileMatchesInput(t, *found, originalInput) + if found.UpdatedAt != original.UpdatedAt { + t.Fatalf("rollback changed updated timestamp: got %s want %s", found.UpdatedAt, original.UpdatedAt) + } +} + +func TestEntRepositoryConcurrentFirstReplacementProducesOneStableProfile(t *testing.T) { + client := openProviderIntegrationClient(t) + ctx := context.Background() + owner := createProviderOwner(t, client) + localities, languages := seededProfileReferences(t, client) + repository := NewEntRepository(client) + input := integrationReplacement(localities[:2], languages[:2]) + + const attempts = 8 + start := make(chan struct{}) + results := make(chan Profile, attempts) + errs := make(chan error, attempts) + var workers sync.WaitGroup + for range attempts { + workers.Add(1) + go func() { + defer workers.Done() + <-start + profile, err := repository.Replace(ctx, owner.ID, input) + if err != nil { + errs <- err + return + } + results <- profile + }() + } + close(start) + workers.Wait() + close(results) + close(errs) + for err := range errs { + t.Fatalf("concurrent replace: %v", err) + } + + var stable *Profile + for profile := range results { + if stable == nil { + value := profile + stable = &value + continue + } + if !reflect.DeepEqual(profile, *stable) { + t.Fatalf("profile = %#v, want stable %#v", profile, *stable) + } + } + if stable == nil { + t.Fatal("no concurrent profile result") + } + assertProfileMatchesInput(t, *stable, input) + + var count int + if err := providerDatabase(t, client).QueryRowContext(ctx, "select count(*) from public.provider_profiles where internal_user_id = $1", owner.ID).Scan(&count); err != nil { + t.Fatalf("count provider profiles: %v", err) + } + if count != 1 { + t.Fatalf("provider profile count = %d, want 1", count) + } +} + +func openProviderIntegrationClient(t *testing.T) *ent.Client { + t.Helper() + databaseURL := os.Getenv("TEST_DATABASE_URL") + if databaseURL == "" { + t.Skip("TEST_DATABASE_URL is required for PostgreSQL integration tests") + } + database, err := sql.Open("pgx", databaseURL) + if err != nil { + t.Fatalf("open database: %v", err) + } + client := ent.NewClient(ent.Driver(entsql.OpenDB(dialect.Postgres, database))) + t.Cleanup(func() { + if err := client.Close(); err != nil { + t.Errorf("close client: %v", err) + } + }) + providerDatabases.Store(client, database) + return client +} + +var providerDatabases sync.Map + +func providerDatabase(t *testing.T, client *ent.Client) *sql.DB { + t.Helper() + value, ok := providerDatabases.Load(client) + if !ok { + t.Fatal("provider database not registered") + } + return value.(*sql.DB) +} + +func createProviderOwner(t *testing.T, client *ent.Client) users.InternalUser { + t.Helper() + ctx := context.Background() + identity := users.VerifiedIdentity{Subject: "test_provider_" + uuid.NewString()} + owner, _, err := users.NewService(users.NewEntRepository(client)).Reconcile(ctx, identity) + if err != nil { + t.Fatalf("reconcile owner: %v", err) + } + accountRepository := accounts.NewEntRepository(client) + if _, err := accountRepository.Create(ctx, owner.ID); err != nil { + t.Fatalf("create owner account: %v", err) + } + if _, err := accountRepository.SetProviderEnabled(ctx, owner.ID, true); err != nil { + t.Fatalf("enable provider: %v", err) + } + t.Cleanup(func() { + if err := client.InternalUser.DeleteOneID(owner.ID).Exec(ctx); err != nil { + t.Errorf("cleanup owner: %v", err) + } + }) + return owner +} + +func seededProfileReferences(t *testing.T, client *ent.Client) ([]uuid.UUID, []string) { + t.Helper() + ctx := context.Background() + localityIDs, err := client.Locality.Query().Where(locality.ActiveEQ(true)).Order(ent.Asc(locality.FieldID)).IDs(ctx) + if err != nil || len(localityIDs) < 2 { + t.Fatalf("seeded localities = %v, err = %v", localityIDs, err) + } + languageIDs, err := client.SpokenLanguage.Query().Where(spokenlanguage.ActiveEQ(true)).Order(ent.Asc(spokenlanguage.FieldID)).IDs(ctx) + if err != nil || len(languageIDs) < 2 { + t.Fatalf("seeded languages = %v, err = %v", languageIDs, err) + } + return localityIDs, languageIDs +} + +func integrationReplacement(localityIDs []uuid.UUID, languageCodes []string) ReplaceProfile { + return ReplaceProfile{ + DisplayName: "Prestador integrado", + ProviderType: ProviderTypeProfessional, + Bio: "Perfil sintético de integração.", + PrimaryLocalityID: localityIDs[0], + ServiceLocalityIDs: append([]uuid.UUID(nil), localityIDs...), + MaxTravelDistanceKM: 25, + TravelsToCustomer: true, + ReceivesCustomer: false, + RemoteServices: false, + LanguageCodes: append([]string(nil), languageCodes...), + } +} + +func assertProfileMatchesInput(t *testing.T, profile Profile, input ReplaceProfile) { + t.Helper() + if profile.DisplayName != input.DisplayName || profile.ProviderType != input.ProviderType || profile.Bio != input.Bio || profile.PrimaryLocalityID != input.PrimaryLocalityID || profile.MaxTravelDistanceKM != input.MaxTravelDistanceKM || profile.TravelsToCustomer != input.TravelsToCustomer || profile.ReceivesCustomer != input.ReceivesCustomer || profile.RemoteServices != input.RemoteServices { + t.Fatalf("profile scalars = %#v, want %#v", profile, input) + } + if !reflect.DeepEqual(profile.ServiceLocalityIDs, input.ServiceLocalityIDs) { + t.Fatalf("service localities = %v, want %v", profile.ServiceLocalityIDs, input.ServiceLocalityIDs) + } + if !reflect.DeepEqual(profile.LanguageCodes, input.LanguageCodes) { + t.Fatalf("language codes = %v, want %v", profile.LanguageCodes, input.LanguageCodes) + } + if profile.CreatedAt.IsZero() || profile.UpdatedAt.IsZero() { + t.Fatalf("profile timestamps are zero: %#v", profile) + } +} diff --git a/backend/internal/providers/model.go b/backend/internal/providers/model.go new file mode 100644 index 0000000..8f854fa --- /dev/null +++ b/backend/internal/providers/model.go @@ -0,0 +1,49 @@ +package providers + +import ( + "errors" + "time" + + "github.com/google/uuid" +) + +var ( + ErrInvalidProfile = errors.New("invalid provider profile") + ErrUnavailable = errors.New("provider profile unavailable") +) + +type ProviderType string + +const ( + ProviderTypeIndividual ProviderType = "individual" + ProviderTypeProfessional ProviderType = "professional" + ProviderTypeBusiness ProviderType = "business" +) + +type ReplaceProfile struct { + DisplayName string + ProviderType ProviderType + Bio string + PrimaryLocalityID uuid.UUID + ServiceLocalityIDs []uuid.UUID + MaxTravelDistanceKM int + TravelsToCustomer bool + ReceivesCustomer bool + RemoteServices bool + LanguageCodes []string +} + +type Profile struct { + DisplayName string + ProviderType ProviderType + Bio string + PrimaryLocalityID uuid.UUID + ServiceLocalityIDs []uuid.UUID + MaxTravelDistanceKM int + TravelsToCustomer bool + ReceivesCustomer bool + RemoteServices bool + LanguageCodes []string + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/backend/internal/providers/repository.go b/backend/internal/providers/repository.go new file mode 100644 index 0000000..f6f2f7f --- /dev/null +++ b/backend/internal/providers/repository.go @@ -0,0 +1,12 @@ +package providers + +import ( + "context" + + "github.com/google/uuid" +) + +type Repository interface { + FindByOwner(context.Context, uuid.UUID) (*Profile, error) + Replace(context.Context, uuid.UUID, ReplaceProfile) (Profile, error) +} diff --git a/backend/internal/providers/service.go b/backend/internal/providers/service.go new file mode 100644 index 0000000..2619736 --- /dev/null +++ b/backend/internal/providers/service.go @@ -0,0 +1,170 @@ +package providers + +import ( + "context" + "errors" + "strings" + "unicode/utf8" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/reference" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" + "github.com/google/uuid" +) + +type ProviderAuthorizer interface { + RequireProvider(context.Context, users.VerifiedIdentity) (users.InternalUser, error) +} + +type ReferenceValidator interface { + ValidateProfileReferences(context.Context, reference.ProfileReferences) error +} + +type Service interface { + Get(context.Context, users.VerifiedIdentity) (*Profile, error) + Put(context.Context, users.VerifiedIdentity, ReplaceProfile) (Profile, error) +} + +type service struct { + authorizer ProviderAuthorizer + repository Repository + references ReferenceValidator +} + +func NewService(authorizer ProviderAuthorizer, repository Repository, references ReferenceValidator) Service { + return service{authorizer: authorizer, repository: repository, references: references} +} + +func (s service) Get(ctx context.Context, identity users.VerifiedIdentity) (*Profile, error) { + owner, err := s.authorize(ctx, identity) + if err != nil { + return nil, err + } + if s.repository == nil { + return nil, ErrUnavailable + } + profile, err := s.repository.FindByOwner(ctx, owner.ID) + if err != nil { + return nil, ErrUnavailable + } + return cloneProfile(profile), nil +} + +func (s service) Put(ctx context.Context, identity users.VerifiedIdentity, input ReplaceProfile) (Profile, error) { + owner, err := s.authorize(ctx, identity) + if err != nil { + return Profile{}, err + } + input, valid := normalizeReplacement(input) + if !valid { + return Profile{}, ErrInvalidProfile + } + if s.references == nil || s.repository == nil { + return Profile{}, ErrUnavailable + } + if err := s.references.ValidateProfileReferences(ctx, reference.ProfileReferences{ + PrimaryLocalityID: input.PrimaryLocalityID, + ServiceLocalityIDs: input.ServiceLocalityIDs, + LanguageCodes: input.LanguageCodes, + }); err != nil { + if errors.Is(err, reference.ErrInvalidReference) { + return Profile{}, ErrInvalidProfile + } + return Profile{}, ErrUnavailable + } + profile, err := s.repository.Replace(ctx, owner.ID, input) + if err != nil { + return Profile{}, ErrUnavailable + } + return *cloneProfile(&profile), nil +} + +func (s service) authorize(ctx context.Context, identity users.VerifiedIdentity) (users.InternalUser, error) { + if s.authorizer == nil { + return users.InternalUser{}, ErrUnavailable + } + return s.authorizer.RequireProvider(ctx, identity) +} + +func normalizeReplacement(input ReplaceProfile) (ReplaceProfile, bool) { + input.DisplayName = strings.TrimSpace(input.DisplayName) + input.Bio = strings.TrimSpace(input.Bio) + input.ServiceLocalityIDs = append([]uuid.UUID(nil), input.ServiceLocalityIDs...) + input.LanguageCodes = append([]string(nil), input.LanguageCodes...) + if utf8.RuneCountInString(input.DisplayName) < 2 || utf8.RuneCountInString(input.DisplayName) > 100 { + return ReplaceProfile{}, false + } + switch input.ProviderType { + case ProviderTypeIndividual, ProviderTypeProfessional, ProviderTypeBusiness: + default: + return ReplaceProfile{}, false + } + if utf8.RuneCountInString(input.Bio) > 1000 || input.PrimaryLocalityID == uuid.Nil { + return ReplaceProfile{}, false + } + if input.MaxTravelDistanceKM < 0 || input.MaxTravelDistanceKM > 200 { + return ReplaceProfile{}, false + } + if !input.TravelsToCustomer && !input.ReceivesCustomer && !input.RemoteServices { + return ReplaceProfile{}, false + } + if input.MaxTravelDistanceKM == 0 && input.TravelsToCustomer && !input.ReceivesCustomer && !input.RemoteServices { + return ReplaceProfile{}, false + } + if !validUUIDSet(input.ServiceLocalityIDs, 1, 20, input.PrimaryLocalityID) { + return ReplaceProfile{}, false + } + if !validLanguageSet(input.LanguageCodes) { + return ReplaceProfile{}, false + } + return input, true +} + +func validUUIDSet(values []uuid.UUID, minimum, maximum int, required uuid.UUID) bool { + if len(values) < minimum || len(values) > maximum { + return false + } + seen := make(map[uuid.UUID]struct{}, len(values)) + hasRequired := false + for _, value := range values { + if value == uuid.Nil { + return false + } + if _, exists := seen[value]; exists { + return false + } + seen[value] = struct{}{} + if value == required { + hasRequired = true + } + } + return hasRequired +} + +func validLanguageSet(values []string) bool { + if len(values) < 1 || len(values) > 10 { + return false + } + seen := make(map[string]struct{}, len(values)) + for index, value := range values { + value = strings.TrimSpace(value) + if value == "" || utf8.RuneCountInString(value) > 10 { + return false + } + if _, exists := seen[value]; exists { + return false + } + seen[value] = struct{}{} + values[index] = value + } + return true +} + +func cloneProfile(profile *Profile) *Profile { + if profile == nil { + return nil + } + cloned := *profile + cloned.ServiceLocalityIDs = append([]uuid.UUID(nil), profile.ServiceLocalityIDs...) + cloned.LanguageCodes = append([]string(nil), profile.LanguageCodes...) + return &cloned +} diff --git a/backend/internal/providers/service_test.go b/backend/internal/providers/service_test.go new file mode 100644 index 0000000..6cbb82e --- /dev/null +++ b/backend/internal/providers/service_test.go @@ -0,0 +1,239 @@ +package providers + +import ( + "context" + "errors" + "reflect" + "strings" + "testing" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/provideraccess" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/reference" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" + "github.com/google/uuid" +) + +func TestServiceGetIsScopedToAuthorizedOwner(t *testing.T) { + t.Parallel() + + owner := providerOwner() + authorizer := &recordingAuthorizer{owner: owner} + repository := &recordingRepository{} + + profile, err := NewService(authorizer, repository, &recordingReferenceValidator{}).Get( + context.Background(), users.VerifiedIdentity{Subject: "user_provider"}, + ) + + if err != nil || profile != nil { + t.Fatalf("profile = %#v, err = %v, want nil nil", profile, err) + } + if repository.owner != owner.ID { + t.Fatalf("repository owner = %s, want %s", repository.owner, owner.ID) + } +} + +func TestServiceRejectsUnauthorizedAndDisabledProviderBeforeRepository(t *testing.T) { + t.Parallel() + + for _, authorizationError := range []error{provideraccess.ErrUnauthorized, provideraccess.ErrForbidden, provideraccess.ErrUnavailable} { + repository := &recordingRepository{} + _, err := NewService( + &recordingAuthorizer{err: authorizationError}, + repository, + &recordingReferenceValidator{}, + ).Get(context.Background(), users.VerifiedIdentity{}) + if !errors.Is(err, authorizationError) { + t.Fatalf("error = %v, want %v", err, authorizationError) + } + if repository.calls != 0 { + t.Fatalf("repository calls = %d, want 0", repository.calls) + } + } +} + +func TestServiceValidatesFullReplacementBeforeReferencesAndRepository(t *testing.T) { + t.Parallel() + + valid := validReplacement() + cases := map[string]ReplaceProfile{ + "short display name": replace(valid, func(value *ReplaceProfile) { value.DisplayName = "A" }), + "invalid provider type": replace(valid, func(value *ReplaceProfile) { value.ProviderType = "admin" }), + "long biography": replace(valid, func(value *ReplaceProfile) { value.Bio = strings.Repeat("x", 1001) }), + "missing primary locality": replace(valid, func(value *ReplaceProfile) { value.PrimaryLocalityID = uuid.Nil }), + "empty service localities": replace(valid, func(value *ReplaceProfile) { value.ServiceLocalityIDs = nil }), + "duplicate localities": replace(valid, func(value *ReplaceProfile) { + value.ServiceLocalityIDs = []uuid.UUID{value.PrimaryLocalityID, value.PrimaryLocalityID} + }), + "primary locality omitted": replace(valid, func(value *ReplaceProfile) { value.ServiceLocalityIDs = []uuid.UUID{uuid.New()} }), + "negative radius": replace(valid, func(value *ReplaceProfile) { value.MaxTravelDistanceKM = -1 }), + "radius too large": replace(valid, func(value *ReplaceProfile) { value.MaxTravelDistanceKM = 201 }), + "no service mode": replace(valid, func(value *ReplaceProfile) { + value.TravelsToCustomer = false + value.ReceivesCustomer = false + value.RemoteServices = false + }), + "zero travel only": replace(valid, func(value *ReplaceProfile) { + value.MaxTravelDistanceKM = 0 + value.TravelsToCustomer = true + value.ReceivesCustomer = false + value.RemoteServices = false + }), + "empty languages": replace(valid, func(value *ReplaceProfile) { value.LanguageCodes = nil }), + "duplicate languages": replace(valid, func(value *ReplaceProfile) { value.LanguageCodes = []string{"pt-PT", "pt-PT"} }), + } + + for name, input := range cases { + name, input := name, input + t.Run(name, func(t *testing.T) { + t.Parallel() + repository := &recordingRepository{} + references := &recordingReferenceValidator{} + _, err := NewService(&recordingAuthorizer{owner: providerOwner()}, repository, references).Put( + context.Background(), users.VerifiedIdentity{Subject: "user_provider"}, input, + ) + if !errors.Is(err, ErrInvalidProfile) { + t.Fatalf("error = %v, want ErrInvalidProfile", err) + } + if references.calls != 0 || repository.calls != 0 { + t.Fatalf("calls = references:%d repository:%d, want none", references.calls, repository.calls) + } + }) + } +} + +func TestServiceRejectsInactiveOrMissingReferences(t *testing.T) { + t.Parallel() + + references := &recordingReferenceValidator{err: reference.ErrInvalidReference} + repository := &recordingRepository{} + _, err := NewService(&recordingAuthorizer{owner: providerOwner()}, repository, references).Put( + context.Background(), users.VerifiedIdentity{Subject: "user_provider"}, validReplacement(), + ) + + if !errors.Is(err, ErrInvalidProfile) { + t.Fatalf("error = %v, want ErrInvalidProfile", err) + } + if repository.calls != 0 { + t.Fatalf("repository calls = %d, want 0", repository.calls) + } +} + +func TestServiceReplacesProfileForAuthorizedOwner(t *testing.T) { + t.Parallel() + + owner := providerOwner() + input := validReplacement() + stored := profileFromReplacement(input) + repository := &recordingRepository{replacement: stored} + references := &recordingReferenceValidator{} + + profile, err := NewService(&recordingAuthorizer{owner: owner}, repository, references).Put( + context.Background(), users.VerifiedIdentity{Subject: "user_provider"}, input, + ) + + if err != nil || !reflect.DeepEqual(profile, stored) { + t.Fatalf("profile = %#v, err = %v, want %#v", profile, err, stored) + } + if repository.owner != owner.ID || !reflect.DeepEqual(repository.input, input) { + t.Fatalf("repository owner/input = %s %#v", repository.owner, repository.input) + } + if references.calls != 1 { + t.Fatalf("reference calls = %d, want 1", references.calls) + } +} + +func TestServiceMapsRepositoryFailureToUnavailable(t *testing.T) { + t.Parallel() + + repository := &recordingRepository{err: errors.New("database private details")} + _, err := NewService(&recordingAuthorizer{owner: providerOwner()}, repository, &recordingReferenceValidator{}).Get( + context.Background(), users.VerifiedIdentity{Subject: "user_provider"}, + ) + if !errors.Is(err, ErrUnavailable) { + t.Fatalf("error = %v, want ErrUnavailable", err) + } +} + +func providerOwner() users.InternalUser { + return users.InternalUser{ID: uuid.MustParse("b00f5bf7-72a8-4a7d-bb23-2ef4c4daf3fb")} +} + +func validReplacement() ReplaceProfile { + primary := uuid.MustParse("9cd8c899-75ad-458d-9e40-a9f8ecdc7e48") + return ReplaceProfile{ + DisplayName: "Prestador local", + ProviderType: ProviderTypeIndividual, + Bio: "Trabalho local de confiança.", + PrimaryLocalityID: primary, + ServiceLocalityIDs: []uuid.UUID{primary}, + MaxTravelDistanceKM: 25, + TravelsToCustomer: true, + ReceivesCustomer: false, + RemoteServices: false, + LanguageCodes: []string{"pt-PT"}, + } +} + +func replace(value ReplaceProfile, mutate func(*ReplaceProfile)) ReplaceProfile { + mutate(&value) + return value +} + +func profileFromReplacement(value ReplaceProfile) Profile { + return Profile{ + DisplayName: value.DisplayName, + ProviderType: value.ProviderType, + Bio: value.Bio, + PrimaryLocalityID: value.PrimaryLocalityID, + ServiceLocalityIDs: value.ServiceLocalityIDs, + MaxTravelDistanceKM: value.MaxTravelDistanceKM, + TravelsToCustomer: value.TravelsToCustomer, + ReceivesCustomer: value.ReceivesCustomer, + RemoteServices: value.RemoteServices, + LanguageCodes: value.LanguageCodes, + CreatedAt: time.Date(2026, 8, 23, 16, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(2026, 8, 23, 16, 0, 0, 0, time.UTC), + } +} + +type recordingAuthorizer struct { + owner users.InternalUser + err error +} + +func (a *recordingAuthorizer) RequireProvider(context.Context, users.VerifiedIdentity) (users.InternalUser, error) { + return a.owner, a.err +} + +type recordingReferenceValidator struct { + err error + calls int +} + +func (v *recordingReferenceValidator) ValidateProfileReferences(context.Context, reference.ProfileReferences) error { + v.calls++ + return v.err +} + +type recordingRepository struct { + profile *Profile + replacement Profile + err error + calls int + owner uuid.UUID + input ReplaceProfile +} + +func (r *recordingRepository) FindByOwner(_ context.Context, owner uuid.UUID) (*Profile, error) { + r.calls++ + r.owner = owner + return r.profile, r.err +} + +func (r *recordingRepository) Replace(_ context.Context, owner uuid.UUID, input ReplaceProfile) (Profile, error) { + r.calls++ + r.owner = owner + r.input = input + return r.replacement, r.err +} From bfd257ee3268829375f53f9858800529dcd9db5c Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 17:42:31 +0100 Subject: [PATCH 18/64] feat: add provider reference and profile API --- backend/cmd/api/main.go | 9 +- backend/internal/httpapi/health_handler.go | 6 +- .../internal/httpapi/openapi_contract_test.go | 38 +++ .../httpapi/provider_profile_handler.go | 169 ++++++++++ .../httpapi/provider_profile_handler_test.go | 159 +++++++++ backend/internal/httpapi/reference_handler.go | 245 ++++++++++++++ .../httpapi/reference_handler_test.go | 135 ++++++++ backend/internal/httpapi/router_test.go | 4 +- frontend/src/shared/api/generated/index.ts | 4 +- frontend/src/shared/api/generated/sdk.gen.ts | 39 ++- .../src/shared/api/generated/types.gen.ts | 273 +++++++++++++++- openapi/juntly-api.v1.yaml | 305 ++++++++++++++++++ 12 files changed, 1379 insertions(+), 7 deletions(-) create mode 100644 backend/internal/httpapi/provider_profile_handler.go create mode 100644 backend/internal/httpapi/provider_profile_handler_test.go create mode 100644 backend/internal/httpapi/reference_handler.go create mode 100644 backend/internal/httpapi/reference_handler_test.go diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index 1fa02fc..ad07846 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -18,6 +18,9 @@ import ( "github.com/SourceSenseiTheRealOne/juntly/backend/internal/accounts" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/health" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/httpapi" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/provideraccess" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/providers" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/reference" "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" _ "github.com/jackc/pgx/v5/stdlib" ) @@ -94,5 +97,9 @@ func newAPIHandler(config runtimeConfig) (http.Handler, io.Closer, error) { healthService := health.NewService(version, time.Now) userService := users.NewService(users.NewEntRepository(client)) accountService := accounts.NewService(userService, accounts.NewEntRepository(client)) - return httpapi.NewRouter(healthService, config.verifier, userService, accountService), client, nil + referenceRepository := reference.NewSQLRepository(database) + referenceService := reference.NewService(referenceRepository) + providerAuthorizer := provideraccess.NewService(userService, accountService) + providerService := providers.NewService(providerAuthorizer, providers.NewEntRepository(client), referenceRepository) + return httpapi.NewRouter(healthService, config.verifier, userService, accountService, referenceService, providerService), client, nil } diff --git a/backend/internal/httpapi/health_handler.go b/backend/internal/httpapi/health_handler.go index 2b72d5b..c1b3f3a 100644 --- a/backend/internal/httpapi/health_handler.go +++ b/backend/internal/httpapi/health_handler.go @@ -35,11 +35,15 @@ func (h HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(h.service.Check(requestID)) } -func NewRouter(service health.Service, verifier authn.Verifier, reconcileService ReconcileService, accountService AccountService) http.Handler { +func NewRouter(service health.Service, verifier authn.Verifier, reconcileService ReconcileService, accountService AccountService, referenceService ReferenceService, providerProfileService ProviderProfileService) http.Handler { mux := http.NewServeMux() mux.Handle("/api/v1/health", NewHealthHandler(service)) + mux.Handle("/api/v1/catalog/categories", NewCategoriesHandler(referenceService)) + mux.Handle("/api/v1/reference/languages", NewLanguagesHandler(referenceService)) + mux.Handle("/api/v1/reference/localities", NewLocalitiesHandler(referenceService)) mux.Handle("/api/v1/auth/reconcile", authn.RequireVerifiedIdentity(verifier, NewReconcileHandler(reconcileService))) mux.Handle("/api/v1/me/account", authn.RequireVerifiedIdentity(verifier, NewAccountHandler(accountService))) + mux.Handle("/api/v1/me/provider-profile", authn.RequireVerifiedIdentity(verifier, NewProviderProfileHandler(providerProfileService))) return mux } diff --git a/backend/internal/httpapi/openapi_contract_test.go b/backend/internal/httpapi/openapi_contract_test.go index 3440aeb..801f91a 100644 --- a/backend/internal/httpapi/openapi_contract_test.go +++ b/backend/internal/httpapi/openapi_contract_test.go @@ -70,3 +70,41 @@ func TestAccountCapabilitiesOpenAPIContract(t *testing.T) { t.Fatal("account contract must declare GET and PUT") } } + +func TestTaxonomyLocationsProviderProfileOpenAPIContract(t *testing.T) { + t.Parallel() + + contract, err := os.ReadFile("../../../openapi/juntly-api.v1.yaml") + if err != nil { + t.Fatalf("read OpenAPI contract: %v", err) + } + contents := string(contract) + for _, required := range []string{ + "/api/v1/catalog/categories:", + "operationId: listServiceCategories", + "/api/v1/reference/languages:", + "operationId: listSpokenLanguages", + "/api/v1/reference/localities:", + "operationId: listLocalities", + "nearLocalityId", + "radiusKm", + "/api/v1/me/provider-profile:", + "operationId: getProviderProfile", + "operationId: replaceProviderProfile", + "ProviderProfileResponse:", + "ReplaceProviderProfileRequest:", + "serviceLocalityIds:", + "languageCodes:", + "FORBIDDEN", + "OpenStreetMap contributors", + } { + if !strings.Contains(contents, required) { + t.Fatalf("OpenAPI contract does not contain %q", required) + } + } + for _, prohibited := range []string{"phoneNumber:", "whatsapp:", "exactAddress:", "latitude:", "longitude:", "internalUserId:"} { + if strings.Contains(contents, prohibited) { + t.Fatalf("OpenAPI contract must not contain %q", prohibited) + } + } +} diff --git a/backend/internal/httpapi/provider_profile_handler.go b/backend/internal/httpapi/provider_profile_handler.go new file mode 100644 index 0000000..f919819 --- /dev/null +++ b/backend/internal/httpapi/provider_profile_handler.go @@ -0,0 +1,169 @@ +package httpapi + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/authn" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/provideraccess" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/providers" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" + "github.com/google/uuid" +) + +const maxProviderProfileRequestBytes = 16 * 1024 + +type ProviderProfileService interface { + Get(context.Context, users.VerifiedIdentity) (*providers.Profile, error) + Put(context.Context, users.VerifiedIdentity, providers.ReplaceProfile) (providers.Profile, error) +} + +type providerProfileHandler struct{ service ProviderProfileService } + +type providerProfileEnvelope struct { + Profile *providerProfileResponse `json:"profile"` +} + +type providerProfileResponse struct { + DisplayName string `json:"displayName"` + ProviderType providers.ProviderType `json:"providerType"` + Bio string `json:"bio"` + PrimaryLocalityID uuid.UUID `json:"primaryLocalityId"` + ServiceLocalityIDs []uuid.UUID `json:"serviceLocalityIds"` + MaxTravelDistanceKM int `json:"maxTravelDistanceKm"` + TravelsToCustomer bool `json:"travelsToCustomer"` + ReceivesCustomer bool `json:"receivesCustomer"` + RemoteServices bool `json:"remoteServices"` + LanguageCodes []string `json:"languageCodes"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + +type replaceProviderProfileRequest struct { + DisplayName *string `json:"displayName"` + ProviderType *string `json:"providerType"` + Bio *string `json:"bio"` + PrimaryLocalityID *uuid.UUID `json:"primaryLocalityId"` + ServiceLocalityIDs *[]uuid.UUID `json:"serviceLocalityIds"` + MaxTravelDistanceKM *int `json:"maxTravelDistanceKm"` + TravelsToCustomer *bool `json:"travelsToCustomer"` + ReceivesCustomer *bool `json:"receivesCustomer"` + RemoteServices *bool `json:"remoteServices"` + LanguageCodes *[]string `json:"languageCodes"` +} + +func NewProviderProfileHandler(service ProviderProfileService) http.Handler { + return providerProfileHandler{service: service} +} + +func (h providerProfileHandler) ServeHTTP(w http.ResponseWriter, request *http.Request) { + if request.Method != http.MethodGet && request.Method != http.MethodPut { + w.Header().Set("Allow", "GET, PUT") + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + requestID := requestIDFromHeader(request.Header.Get(RequestIDHeader)) + identity, ok := authn.IdentityFromContext(request.Context()) + if !ok { + writeAPIError(w, http.StatusUnauthorized, "UNAUTHORIZED", "Unauthorized", requestID) + return + } + if h.service == nil { + writeAPIError(w, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "Service unavailable", requestID) + return + } + + if request.Method == http.MethodGet { + profile, err := h.service.Get(request.Context(), identity) + if err != nil { + writeProviderProfileError(w, err, requestID) + return + } + var response *providerProfileResponse + if profile != nil { + value := providerProfileResponseFromDomain(*profile) + response = &value + } + writeJSON(w, http.StatusOK, providerProfileEnvelope{Profile: response}, requestID) + return + } + + input, valid := decodeProviderProfileRequest(request.Body) + if !valid { + writeAPIError(w, http.StatusBadRequest, "INVALID_REQUEST", "Invalid request", requestID) + return + } + profile, err := h.service.Put(request.Context(), identity, input) + if err != nil { + writeProviderProfileError(w, err, requestID) + return + } + response := providerProfileResponseFromDomain(profile) + writeJSON(w, http.StatusOK, providerProfileEnvelope{Profile: &response}, requestID) +} + +func decodeProviderProfileRequest(body io.Reader) (providers.ReplaceProfile, bool) { + decoder := json.NewDecoder(io.LimitReader(body, maxProviderProfileRequestBytes+1)) + decoder.DisallowUnknownFields() + var value replaceProviderProfileRequest + if err := decoder.Decode(&value); err != nil || !completeProviderProfileRequest(value) { + return providers.ReplaceProfile{}, false + } + var extra any + if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) { + return providers.ReplaceProfile{}, false + } + return providers.ReplaceProfile{ + DisplayName: *value.DisplayName, + ProviderType: providers.ProviderType(*value.ProviderType), + Bio: *value.Bio, + PrimaryLocalityID: *value.PrimaryLocalityID, + ServiceLocalityIDs: append([]uuid.UUID(nil), (*value.ServiceLocalityIDs)...), + MaxTravelDistanceKM: *value.MaxTravelDistanceKM, + TravelsToCustomer: *value.TravelsToCustomer, + ReceivesCustomer: *value.ReceivesCustomer, + RemoteServices: *value.RemoteServices, + LanguageCodes: append([]string(nil), (*value.LanguageCodes)...), + }, true +} + +func completeProviderProfileRequest(value replaceProviderProfileRequest) bool { + return value.DisplayName != nil && value.ProviderType != nil && value.Bio != nil && + value.PrimaryLocalityID != nil && value.ServiceLocalityIDs != nil && + value.MaxTravelDistanceKM != nil && value.TravelsToCustomer != nil && + value.ReceivesCustomer != nil && value.RemoteServices != nil && value.LanguageCodes != nil +} + +func providerProfileResponseFromDomain(profile providers.Profile) providerProfileResponse { + return providerProfileResponse{ + DisplayName: profile.DisplayName, + ProviderType: profile.ProviderType, + Bio: profile.Bio, + PrimaryLocalityID: profile.PrimaryLocalityID, + ServiceLocalityIDs: append([]uuid.UUID(nil), profile.ServiceLocalityIDs...), + MaxTravelDistanceKM: profile.MaxTravelDistanceKM, + TravelsToCustomer: profile.TravelsToCustomer, + ReceivesCustomer: profile.ReceivesCustomer, + RemoteServices: profile.RemoteServices, + LanguageCodes: append([]string(nil), profile.LanguageCodes...), + CreatedAt: profile.CreatedAt.UTC().Format(time.RFC3339Nano), + UpdatedAt: profile.UpdatedAt.UTC().Format(time.RFC3339Nano), + } +} + +func writeProviderProfileError(w http.ResponseWriter, err error, requestID string) { + switch { + case errors.Is(err, provideraccess.ErrUnauthorized): + writeAPIError(w, http.StatusUnauthorized, "UNAUTHORIZED", "Unauthorized", requestID) + case errors.Is(err, provideraccess.ErrForbidden): + writeAPIError(w, http.StatusForbidden, "FORBIDDEN", "Forbidden", requestID) + case errors.Is(err, providers.ErrInvalidProfile): + writeAPIError(w, http.StatusBadRequest, "INVALID_REQUEST", "Invalid request", requestID) + default: + writeAPIError(w, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "Service unavailable", requestID) + } +} diff --git a/backend/internal/httpapi/provider_profile_handler_test.go b/backend/internal/httpapi/provider_profile_handler_test.go new file mode 100644 index 0000000..4a19e1b --- /dev/null +++ b/backend/internal/httpapi/provider_profile_handler_test.go @@ -0,0 +1,159 @@ +package httpapi_test + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/authn" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/httpapi" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/provideraccess" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/providers" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/users" + "github.com/google/uuid" +) + +func TestProviderProfileHandlerRequiresVerifiedIdentity(t *testing.T) { + t.Parallel() + + service := &recordingProviderProfileService{} + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/api/v1/me/provider-profile", nil) + request.Header.Set(httpapi.RequestIDHeader, "req_provider_unauthorized") + + httpapi.NewProviderProfileHandler(service).ServeHTTP(response, request) + + if response.Code != http.StatusUnauthorized || service.calls != 0 { + t.Fatalf("status/calls = %d/%d, want 401/0", response.Code, service.calls) + } + assertErrorResponse(t, response, "UNAUTHORIZED", "Unauthorized", "req_provider_unauthorized") +} + +func TestProviderProfileHandlerReturnsNullableAndClosedOwnerProfile(t *testing.T) { + t.Parallel() + + for name, profile := range map[string]*providers.Profile{ + "missing": nil, + "existing": { + DisplayName: "Prestador local", + ProviderType: providers.ProviderTypeProfessional, + Bio: "Serviço de confiança.", + PrimaryLocalityID: uuid.MustParse("11111111-1111-4111-8111-111111111111"), + ServiceLocalityIDs: []uuid.UUID{uuid.MustParse("11111111-1111-4111-8111-111111111111")}, + MaxTravelDistanceKM: 25, + TravelsToCustomer: true, + LanguageCodes: []string{"pt-PT"}, + CreatedAt: time.Date(2026, 8, 23, 16, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(2026, 8, 23, 16, 0, 0, 0, time.UTC), + }, + } { + name, profile := name, profile + t.Run(name, func(t *testing.T) { + t.Parallel() + service := &recordingProviderProfileService{profile: profile} + handler := authn.RequireVerifiedIdentity(staticVerifier{identity: users.VerifiedIdentity{Subject: "user_provider"}}, httpapi.NewProviderProfileHandler(service)) + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/api/v1/me/provider-profile", nil) + request.Header.Set("Authorization", "Bearer synthetic-token") + request.Header.Set(httpapi.RequestIDHeader, "req_provider_get") + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusOK { + t.Fatalf("status = %d", response.Code) + } + var body map[string]any + if err := json.NewDecoder(response.Body).Decode(&body); err != nil { + t.Fatalf("decode: %v", err) + } + if len(body) != 1 { + t.Fatalf("body fields = %#v", body) + } + serialized := mustJSON(t, body) + for _, prohibited := range []string{"user_provider", "synthetic-token", "internalUserId", "clerk", "phone", "address"} { + if strings.Contains(serialized, prohibited) { + t.Fatalf("response exposes %q: %s", prohibited, serialized) + } + } + }) + } +} + +func TestProviderProfileHandlerPUTStrictlyDecodesReplacement(t *testing.T) { + t.Parallel() + + service := &recordingProviderProfileService{replacement: providers.Profile{DisplayName: "Prestador local", ProviderType: providers.ProviderTypeIndividual, Bio: "", PrimaryLocalityID: uuid.MustParse("11111111-1111-4111-8111-111111111111"), ServiceLocalityIDs: []uuid.UUID{uuid.MustParse("11111111-1111-4111-8111-111111111111")}, MaxTravelDistanceKM: 10, TravelsToCustomer: true, LanguageCodes: []string{"pt-PT"}, CreatedAt: time.Now(), UpdatedAt: time.Now()}} + handler := authn.RequireVerifiedIdentity(staticVerifier{identity: users.VerifiedIdentity{Subject: "user_provider"}}, httpapi.NewProviderProfileHandler(service)) + valid := `{"displayName":"Prestador local","providerType":"individual","bio":"","primaryLocalityId":"11111111-1111-4111-8111-111111111111","serviceLocalityIds":["11111111-1111-4111-8111-111111111111"],"maxTravelDistanceKm":10,"travelsToCustomer":true,"receivesCustomer":false,"remoteServices":false,"languageCodes":["pt-PT"]}` + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPut, "/api/v1/me/provider-profile", strings.NewReader(valid)) + request.Header.Set("Authorization", "Bearer synthetic-token") + request.Header.Set(httpapi.RequestIDHeader, "req_provider_put") + + handler.ServeHTTP(response, request) + + if response.Code != http.StatusOK || service.putCalls != 1 || service.input.DisplayName != "Prestador local" { + t.Fatalf("status/calls/input = %d/%d/%#v", response.Code, service.putCalls, service.input) + } + + for _, body := range []string{`{}`, `null`, valid + `{}`, strings.Replace(valid, `}`, `,"admin":true}`, 1)} { + service := &recordingProviderProfileService{} + handler := authn.RequireVerifiedIdentity(staticVerifier{identity: users.VerifiedIdentity{Subject: "user_provider"}}, httpapi.NewProviderProfileHandler(service)) + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPut, "/api/v1/me/provider-profile", strings.NewReader(body)) + request.Header.Set("Authorization", "Bearer synthetic-token") + request.Header.Set(httpapi.RequestIDHeader, "req_provider_invalid") + handler.ServeHTTP(response, request) + if response.Code != http.StatusBadRequest || service.putCalls != 0 { + t.Fatalf("invalid body status/calls = %d/%d", response.Code, service.putCalls) + } + } +} + +func TestProviderProfileHandlerMapsForbiddenAndUnavailable(t *testing.T) { + t.Parallel() + + for _, test := range []struct { + err error + status int + code string + }{ + {provideraccess.ErrForbidden, http.StatusForbidden, "FORBIDDEN"}, + {provideraccess.ErrUnavailable, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE"}, + } { + handler := authn.RequireVerifiedIdentity( + staticVerifier{identity: users.VerifiedIdentity{Subject: "user_provider"}}, + httpapi.NewProviderProfileHandler(&recordingProviderProfileService{err: test.err}), + ) + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/api/v1/me/provider-profile", nil) + request.Header.Set("Authorization", "Bearer synthetic-token") + handler.ServeHTTP(response, request) + if response.Code != test.status || !strings.Contains(response.Body.String(), test.code) { + t.Fatalf("error %v status/body = %d/%s", test.err, response.Code, response.Body.String()) + } + } +} + +type recordingProviderProfileService struct { + profile *providers.Profile + replacement providers.Profile + err error + calls int + putCalls int + input providers.ReplaceProfile +} + +func (s *recordingProviderProfileService) Get(context.Context, users.VerifiedIdentity) (*providers.Profile, error) { + s.calls++ + return s.profile, s.err +} +func (s *recordingProviderProfileService) Put(_ context.Context, _ users.VerifiedIdentity, input providers.ReplaceProfile) (providers.Profile, error) { + s.putCalls++ + s.input = input + return s.replacement, s.err +} diff --git a/backend/internal/httpapi/reference_handler.go b/backend/internal/httpapi/reference_handler.go new file mode 100644 index 0000000..9d1f482 --- /dev/null +++ b/backend/internal/httpapi/reference_handler.go @@ -0,0 +1,245 @@ +package httpapi + +import ( + "context" + "errors" + "net/http" + "net/url" + "strconv" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/reference" + "github.com/google/uuid" +) + +type ReferenceService interface { + Categories(context.Context, string) ([]reference.Category, error) + Languages(context.Context, string) ([]reference.Language, error) + Localities(context.Context, string) ([]reference.Locality, error) + NearbyLocalities(context.Context, uuid.UUID, int, string) ([]reference.LocalityDistance, error) +} + +type referenceKind string + +const ( + referenceCategories referenceKind = "categories" + referenceLanguages referenceKind = "languages" + referenceLocalities referenceKind = "localities" +) + +type referenceHandler struct { + service ReferenceService + kind referenceKind +} + +type categoryResponse struct { + ID uuid.UUID `json:"id"` + ParentID *uuid.UUID `json:"parentId"` + Slug string `json:"slug"` + Name string `json:"name"` +} + +type categoriesResponse struct { + Categories []categoryResponse `json:"categories"` +} + +type languageResponse struct { + Code string `json:"code"` + Name string `json:"name"` +} + +type languagesResponse struct { + Languages []languageResponse `json:"languages"` +} + +type localityResponse struct { + ID uuid.UUID `json:"id"` + Slug string `json:"slug"` + Name string `json:"name"` + ParishName string `json:"parishName"` + MunicipalityName string `json:"municipalityName"` + DistrictName string `json:"districtName"` + DistanceMeters *int `json:"distanceMeters,omitempty"` +} + +type attributionResponse struct { + Text string `json:"text"` + URL string `json:"url"` +} + +type localitiesResponse struct { + Localities []localityResponse `json:"localities"` + Attribution attributionResponse `json:"attribution"` +} + +func NewCategoriesHandler(service ReferenceService) http.Handler { + return referenceHandler{service: service, kind: referenceCategories} +} + +func NewLanguagesHandler(service ReferenceService) http.Handler { + return referenceHandler{service: service, kind: referenceLanguages} +} + +func NewLocalitiesHandler(service ReferenceService) http.Handler { + return referenceHandler{service: service, kind: referenceLocalities} +} + +func (h referenceHandler) ServeHTTP(w http.ResponseWriter, request *http.Request) { + if request.Method != http.MethodGet { + w.Header().Set("Allow", http.MethodGet) + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + requestID := requestIDFromHeader(request.Header.Get(RequestIDHeader)) + if h.service == nil { + writeAPIError(w, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "Service unavailable", requestID) + return + } + locale, ok := exactQueryValue(request.URL.Query(), "locale") + if !ok || !validReferenceLocale(locale) { + writeAPIError(w, http.StatusBadRequest, "INVALID_REQUEST", "Invalid request", requestID) + return + } + + switch h.kind { + case referenceCategories: + if !exactQueryKeys(request.URL.Query(), "locale") { + writeAPIError(w, http.StatusBadRequest, "INVALID_REQUEST", "Invalid request", requestID) + return + } + values, err := h.service.Categories(request.Context(), locale) + if err != nil { + writeReferenceError(w, err, requestID) + return + } + response := categoriesResponse{Categories: make([]categoryResponse, len(values))} + for index, value := range values { + response.Categories[index] = categoryResponse{ID: value.ID, ParentID: value.ParentID, Slug: value.Slug, Name: value.Name} + } + writeJSON(w, http.StatusOK, response, requestID) + case referenceLanguages: + if !exactQueryKeys(request.URL.Query(), "locale") { + writeAPIError(w, http.StatusBadRequest, "INVALID_REQUEST", "Invalid request", requestID) + return + } + values, err := h.service.Languages(request.Context(), locale) + if err != nil { + writeReferenceError(w, err, requestID) + return + } + response := languagesResponse{Languages: make([]languageResponse, len(values))} + for index, value := range values { + response.Languages[index] = languageResponse{Code: value.Code, Name: value.Name} + } + writeJSON(w, http.StatusOK, response, requestID) + case referenceLocalities: + h.serveLocalities(w, request, locale, requestID) + default: + writeAPIError(w, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "Service unavailable", requestID) + } +} + +func validReferenceLocale(locale string) bool { + switch locale { + case "pt-PT", "en", "es": + return true + default: + return false + } +} + +func (h referenceHandler) serveLocalities(w http.ResponseWriter, request *http.Request, locale, requestID string) { + query := request.URL.Query() + if !exactQueryKeys(query, "locale", "nearLocalityId", "radiusKm") { + writeAPIError(w, http.StatusBadRequest, "INVALID_REQUEST", "Invalid request", requestID) + return + } + nearValue, hasNear := optionalExactQueryValue(query, "nearLocalityId") + radiusValue, hasRadius := optionalExactQueryValue(query, "radiusKm") + if hasNear != hasRadius { + writeAPIError(w, http.StatusBadRequest, "INVALID_REQUEST", "Invalid request", requestID) + return + } + + response := localitiesResponse{Attribution: attributionResponse{Text: reference.AttributionText, URL: reference.AttributionURL}} + if !hasNear { + values, err := h.service.Localities(request.Context(), locale) + if err != nil { + writeReferenceError(w, err, requestID) + return + } + response.Localities = localityResponses(values) + writeJSON(w, http.StatusOK, response, requestID) + return + } + origin, err := uuid.Parse(nearValue) + if err != nil { + writeAPIError(w, http.StatusBadRequest, "INVALID_REQUEST", "Invalid request", requestID) + return + } + radius, err := strconv.Atoi(radiusValue) + if err != nil { + writeAPIError(w, http.StatusBadRequest, "INVALID_REQUEST", "Invalid request", requestID) + return + } + values, err := h.service.NearbyLocalities(request.Context(), origin, radius, locale) + if err != nil { + writeReferenceError(w, err, requestID) + return + } + response.Localities = make([]localityResponse, len(values)) + for index, value := range values { + distance := value.DistanceMeters + response.Localities[index] = localityResponse{ID: value.ID, Slug: value.Slug, Name: value.Name, ParishName: value.ParishName, MunicipalityName: value.MunicipalityName, DistrictName: value.DistrictName, DistanceMeters: &distance} + } + writeJSON(w, http.StatusOK, response, requestID) +} + +func localityResponses(values []reference.Locality) []localityResponse { + responses := make([]localityResponse, len(values)) + for index, value := range values { + responses[index] = localityResponse{ID: value.ID, Slug: value.Slug, Name: value.Name, ParishName: value.ParishName, MunicipalityName: value.MunicipalityName, DistrictName: value.DistrictName} + } + return responses +} + +func writeReferenceError(w http.ResponseWriter, err error, requestID string) { + if errors.Is(err, reference.ErrInvalidRequest) || errors.Is(err, reference.ErrNotFound) { + writeAPIError(w, http.StatusBadRequest, "INVALID_REQUEST", "Invalid request", requestID) + return + } + writeAPIError(w, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "Service unavailable", requestID) +} + +func exactQueryKeys(values url.Values, allowed ...string) bool { + allowlist := make(map[string]struct{}, len(allowed)) + for _, key := range allowed { + allowlist[key] = struct{}{} + } + for key := range values { + if _, ok := allowlist[key]; !ok { + return false + } + } + return true +} + +func exactQueryValue(values url.Values, key string) (string, bool) { + items, ok := values[key] + return firstExact(items, ok) +} + +func optionalExactQueryValue(values url.Values, key string) (string, bool) { + items, ok := values[key] + if !ok { + return "", false + } + return firstExact(items, true) +} + +func firstExact(items []string, present bool) (string, bool) { + returnValue := "" + if !present || len(items) != 1 || items[0] == "" { + return returnValue, false + } + return items[0], true +} diff --git a/backend/internal/httpapi/reference_handler_test.go b/backend/internal/httpapi/reference_handler_test.go new file mode 100644 index 0000000..1cf0d05 --- /dev/null +++ b/backend/internal/httpapi/reference_handler_test.go @@ -0,0 +1,135 @@ +package httpapi_test + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/httpapi" + "github.com/SourceSenseiTheRealOne/juntly/backend/internal/reference" + "github.com/google/uuid" +) + +func TestReferenceHandlersReturnClosedPublicCatalogs(t *testing.T) { + t.Parallel() + + parentID := uuid.MustParse("11111111-1111-4111-8111-111111111111") + categoryID := uuid.MustParse("22222222-2222-4222-8222-222222222222") + localityID := uuid.MustParse("33333333-3333-4333-8333-333333333333") + service := &recordingReferenceService{ + categories: []reference.Category{{ID: categoryID, ParentID: &parentID, Slug: "cleaning", Name: "Limpeza"}}, + languages: []reference.Language{{Code: "pt-PT", Name: "Português"}}, + localities: []reference.Locality{{ID: localityID, Slug: "zebreira", Name: "Zebreira", ParishName: "Zebreira e Segura", MunicipalityName: "Idanha-a-Nova", DistrictName: "Castelo Branco"}}, + } + + for path, handler := range map[string]http.Handler{ + "/api/v1/catalog/categories?locale=pt-PT": httpapi.NewCategoriesHandler(service), + "/api/v1/reference/languages?locale=pt-PT": httpapi.NewLanguagesHandler(service), + "/api/v1/reference/localities?locale=pt-PT": httpapi.NewLocalitiesHandler(service), + } { + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, path, nil) + request.Header.Set(httpapi.RequestIDHeader, "req_reference_public") + handler.ServeHTTP(response, request) + if response.Code != http.StatusOK { + t.Fatalf("%s status = %d", path, response.Code) + } + if response.Header().Get(httpapi.RequestIDHeader) != "req_reference_public" { + t.Fatalf("%s request ID mismatch", path) + } + body := response.Body.String() + for _, prohibited := range []string{"latitude", "longitude", "internalUser", "clerk", "phone", "address"} { + if strings.Contains(strings.ToLower(body), strings.ToLower(prohibited)) { + t.Fatalf("%s response exposes %q: %s", path, prohibited, body) + } + } + } +} + +func TestLocalitiesHandlerSupportsPairedRadiusQuery(t *testing.T) { + t.Parallel() + + origin := uuid.MustParse("33333333-3333-4333-8333-333333333333") + service := &recordingReferenceService{nearby: []reference.LocalityDistance{{Locality: reference.Locality{ID: origin, Slug: "zebreira", Name: "Zebreira", ParishName: "Zebreira e Segura", MunicipalityName: "Idanha-a-Nova", DistrictName: "Castelo Branco"}, DistanceMeters: 0}}} + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/api/v1/reference/localities?locale=pt-PT&nearLocalityId="+origin.String()+"&radiusKm=25", nil) + request.Header.Set(httpapi.RequestIDHeader, "req_reference_radius") + + httpapi.NewLocalitiesHandler(service).ServeHTTP(response, request) + + if response.Code != http.StatusOK || service.nearbyCalls != 1 || service.origin != origin || service.radius != 25 { + t.Fatalf("status/calls/origin/radius = %d %d %s %d", response.Code, service.nearbyCalls, service.origin, service.radius) + } + if !strings.Contains(response.Body.String(), `"distanceMeters":0`) { + t.Fatalf("radius response = %s", response.Body.String()) + } +} + +func TestReferenceHandlersRejectUnknownOrUnpairedQueriesBeforeService(t *testing.T) { + t.Parallel() + + cases := []struct { + path string + handler http.Handler + }{ + {"/api/v1/catalog/categories?locale=pt-PT&admin=true", httpapi.NewCategoriesHandler(&recordingReferenceService{})}, + {"/api/v1/reference/languages?locale=fr", httpapi.NewLanguagesHandler(&recordingReferenceService{})}, + {"/api/v1/reference/localities?locale=pt-PT&radiusKm=25", httpapi.NewLocalitiesHandler(&recordingReferenceService{})}, + {"/api/v1/reference/localities?locale=pt-PT&nearLocalityId=not-a-uuid&radiusKm=25", httpapi.NewLocalitiesHandler(&recordingReferenceService{})}, + } + for _, item := range cases { + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, item.path, nil) + request.Header.Set(httpapi.RequestIDHeader, "req_reference_invalid") + item.handler.ServeHTTP(response, request) + if response.Code != http.StatusBadRequest { + t.Fatalf("%s status = %d, want 400", item.path, response.Code) + } + assertErrorResponse(t, response, "INVALID_REQUEST", "Invalid request", "req_reference_invalid") + } +} + +func TestReferenceHandlerReturnsSafeUnavailable(t *testing.T) { + t.Parallel() + + service := &recordingReferenceService{err: reference.ErrUnavailable} + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/api/v1/catalog/categories?locale=en", nil) + request.Header.Set(httpapi.RequestIDHeader, "req_reference_unavailable") + + httpapi.NewCategoriesHandler(service).ServeHTTP(response, request) + + if response.Code != http.StatusServiceUnavailable { + t.Fatalf("status = %d, want 503", response.Code) + } + assertErrorResponse(t, response, "SERVICE_UNAVAILABLE", "Service unavailable", "req_reference_unavailable") +} + +type recordingReferenceService struct { + categories []reference.Category + languages []reference.Language + localities []reference.Locality + nearby []reference.LocalityDistance + err error + nearbyCalls int + origin uuid.UUID + radius int +} + +func (s *recordingReferenceService) Categories(context.Context, string) ([]reference.Category, error) { + return s.categories, s.err +} +func (s *recordingReferenceService) Languages(context.Context, string) ([]reference.Language, error) { + return s.languages, s.err +} +func (s *recordingReferenceService) Localities(context.Context, string) ([]reference.Locality, error) { + return s.localities, s.err +} +func (s *recordingReferenceService) NearbyLocalities(_ context.Context, origin uuid.UUID, radius int, _ string) ([]reference.LocalityDistance, error) { + s.nearbyCalls++ + s.origin = origin + s.radius = radius + return s.nearby, s.err +} diff --git a/backend/internal/httpapi/router_test.go b/backend/internal/httpapi/router_test.go index 2bf52f1..fc5b346 100644 --- a/backend/internal/httpapi/router_test.go +++ b/backend/internal/httpapi/router_test.go @@ -30,7 +30,9 @@ func TestRouterLeavesHealthPublicAndProtectsReconciliation(t *testing.T) { ProviderEnabled: false, OnboardingCompletedAt: time.Date(2026, 8, 23, 12, 5, 0, 0, time.UTC), }} - router := httpapi.NewRouter(healthService, verifier, reconcileService, accountService) + referenceService := &recordingReferenceService{} + providerProfileService := &recordingProviderProfileService{} + router := httpapi.NewRouter(healthService, verifier, reconcileService, accountService, referenceService, providerProfileService) healthResponse := httptest.NewRecorder() router.ServeHTTP(healthResponse, httptest.NewRequest(http.MethodGet, "/api/v1/health", nil)) diff --git a/frontend/src/shared/api/generated/index.ts b/frontend/src/shared/api/generated/index.ts index 84abeca..9fafefb 100644 --- a/frontend/src/shared/api/generated/index.ts +++ b/frontend/src/shared/api/generated/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { getAccountCapabilities, getHealth, type Options, reconcileInternalUser, updateAccountCapabilities } from './sdk.gen'; -export type { AccountCapabilitiesResponse, ClientOptions, ErrorCode, ErrorDetail, ErrorResponse, GetAccountCapabilitiesData, GetAccountCapabilitiesError, GetAccountCapabilitiesErrors, GetAccountCapabilitiesResponse, GetAccountCapabilitiesResponses, GetHealthData, GetHealthError, GetHealthErrors, GetHealthResponse, GetHealthResponses, HealthResponse, HealthStatus, InternalUserResponse, ReconcileInternalUserData, ReconcileInternalUserError, ReconcileInternalUserErrors, ReconcileInternalUserResponse, ReconcileInternalUserResponses, RequestId, RequestIdHeader, UpdateAccountCapabilitiesData, UpdateAccountCapabilitiesError, UpdateAccountCapabilitiesErrors, UpdateAccountCapabilitiesRequest, UpdateAccountCapabilitiesResponse, UpdateAccountCapabilitiesResponses } from './types.gen'; +export { getAccountCapabilities, getHealth, getProviderProfile, listLocalities, listServiceCategories, listSpokenLanguages, type Options, reconcileInternalUser, replaceProviderProfile, updateAccountCapabilities } from './sdk.gen'; +export type { AccountCapabilitiesResponse, Attribution, CategoriesResponse, Category, ClientOptions, ErrorCode, ErrorDetail, ErrorResponse, GetAccountCapabilitiesData, GetAccountCapabilitiesError, GetAccountCapabilitiesErrors, GetAccountCapabilitiesResponse, GetAccountCapabilitiesResponses, GetHealthData, GetHealthError, GetHealthErrors, GetHealthResponse, GetHealthResponses, GetProviderProfileData, GetProviderProfileError, GetProviderProfileErrors, GetProviderProfileResponse, GetProviderProfileResponses, HealthResponse, HealthStatus, InternalUserResponse, LanguagesResponse, ListLocalitiesData, ListLocalitiesError, ListLocalitiesErrors, ListLocalitiesResponse, ListLocalitiesResponses, ListServiceCategoriesData, ListServiceCategoriesError, ListServiceCategoriesErrors, ListServiceCategoriesResponse, ListServiceCategoriesResponses, ListSpokenLanguagesData, ListSpokenLanguagesError, ListSpokenLanguagesErrors, ListSpokenLanguagesResponse, ListSpokenLanguagesResponses, LocaleQuery, LocalitiesResponse, Locality, ProviderProfileEnvelope, ProviderProfileResponse, ProviderType, ReconcileInternalUserData, ReconcileInternalUserError, ReconcileInternalUserErrors, ReconcileInternalUserResponse, ReconcileInternalUserResponses, ReplaceProviderProfileData, ReplaceProviderProfileError, ReplaceProviderProfileErrors, ReplaceProviderProfileRequest, ReplaceProviderProfileResponse, ReplaceProviderProfileResponses, RequestId, RequestIdHeader, SpokenLanguage, UpdateAccountCapabilitiesData, UpdateAccountCapabilitiesError, UpdateAccountCapabilitiesErrors, UpdateAccountCapabilitiesRequest, UpdateAccountCapabilitiesResponse, UpdateAccountCapabilitiesResponses } from './types.gen'; diff --git a/frontend/src/shared/api/generated/sdk.gen.ts b/frontend/src/shared/api/generated/sdk.gen.ts index afa7d30..305b8df 100644 --- a/frontend/src/shared/api/generated/sdk.gen.ts +++ b/frontend/src/shared/api/generated/sdk.gen.ts @@ -2,7 +2,7 @@ import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; -import type { GetAccountCapabilitiesData, GetAccountCapabilitiesErrors, GetAccountCapabilitiesResponses, GetHealthData, GetHealthErrors, GetHealthResponses, ReconcileInternalUserData, ReconcileInternalUserErrors, ReconcileInternalUserResponses, UpdateAccountCapabilitiesData, UpdateAccountCapabilitiesErrors, UpdateAccountCapabilitiesResponses } from './types.gen'; +import type { GetAccountCapabilitiesData, GetAccountCapabilitiesErrors, GetAccountCapabilitiesResponses, GetHealthData, GetHealthErrors, GetHealthResponses, GetProviderProfileData, GetProviderProfileErrors, GetProviderProfileResponses, ListLocalitiesData, ListLocalitiesErrors, ListLocalitiesResponses, ListServiceCategoriesData, ListServiceCategoriesErrors, ListServiceCategoriesResponses, ListSpokenLanguagesData, ListSpokenLanguagesErrors, ListSpokenLanguagesResponses, ReconcileInternalUserData, ReconcileInternalUserErrors, ReconcileInternalUserResponses, ReplaceProviderProfileData, ReplaceProviderProfileErrors, ReplaceProviderProfileResponses, UpdateAccountCapabilitiesData, UpdateAccountCapabilitiesErrors, UpdateAccountCapabilitiesResponses } from './types.gen'; export type Options = Options2 & { /** @@ -47,6 +47,43 @@ export const updateAccountCapabilities = ( } }); +/** + * List active service categories in one supported locale. + */ +export const listServiceCategories = (options: Options) => (options.client ?? client).get({ url: '/api/v1/catalog/categories', ...options }); + +/** + * List active spoken languages in one supported locale. + */ +export const listSpokenLanguages = (options: Options) => (options.client ?? client).get({ url: '/api/v1/reference/languages', ...options }); + +/** + * List active launch localities, optionally within a radius. + */ +export const listLocalities = (options: Options) => (options.client ?? client).get({ url: '/api/v1/reference/localities', ...options }); + +/** + * Read the current provider's owner-only profile. + */ +export const getProviderProfile = (options?: Options) => (options?.client ?? client).get({ + security: [{ scheme: 'bearer', type: 'http' }], + url: '/api/v1/me/provider-profile', + ...options +}); + +/** + * Create or fully replace the current provider's owner-only profile. + */ +export const replaceProviderProfile = (options: Options) => (options.client ?? client).put({ + security: [{ scheme: 'bearer', type: 'http' }], + url: '/api/v1/me/provider-profile', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + /** * Reconcile the current verified session to an internal user. * diff --git a/frontend/src/shared/api/generated/types.gen.ts b/frontend/src/shared/api/generated/types.gen.ts index 168dc8e..cf832ea 100644 --- a/frontend/src/shared/api/generated/types.gen.ts +++ b/frontend/src/shared/api/generated/types.gen.ts @@ -16,7 +16,7 @@ export type HealthResponse = { requestId: RequestId; }; -export type ErrorCode = 'INVALID_REQUEST' | 'UNAUTHORIZED' | 'SERVICE_UNAVAILABLE'; +export type ErrorCode = 'INVALID_REQUEST' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'SERVICE_UNAVAILABLE'; export type ErrorDetail = { code: ErrorCode; @@ -43,11 +43,87 @@ export type UpdateAccountCapabilitiesRequest = { providerEnabled: boolean; }; +export type Category = { + id: string; + parentId: string | null; + slug: string; + name: string; +}; + +export type CategoriesResponse = { + categories: Array; +}; + +export type SpokenLanguage = { + code: string; + name: string; +}; + +export type LanguagesResponse = { + languages: Array; +}; + +export type Attribution = { + text: '© OpenStreetMap contributors'; + url: 'https://www.openstreetmap.org/copyright'; +}; + +export type Locality = { + id: string; + slug: string; + name: string; + parishName: string; + municipalityName: string; + districtName: string; + distanceMeters?: number; +}; + +export type LocalitiesResponse = { + localities: Array; + attribution: Attribution; +}; + +export type ProviderType = 'individual' | 'professional' | 'business'; + +export type ReplaceProviderProfileRequest = { + displayName: string; + providerType: ProviderType; + bio: string; + primaryLocalityId: string; + serviceLocalityIds: Array; + maxTravelDistanceKm: number; + travelsToCustomer: boolean; + receivesCustomer: boolean; + remoteServices: boolean; + languageCodes: Array; +}; + +export type ProviderProfileResponse = { + displayName: string; + providerType: ProviderType; + bio: string; + primaryLocalityId: string; + serviceLocalityIds: Array; + maxTravelDistanceKm: number; + travelsToCustomer: boolean; + receivesCustomer: boolean; + remoteServices: boolean; + languageCodes: Array; + createdAt: string; + updatedAt: string; +}; + +export type ProviderProfileEnvelope = { + profile: ProviderProfileResponse | null; +}; + /** * Optional client-supplied correlation identifier. */ export type RequestIdHeader = RequestId; +export type LocaleQuery = 'pt-PT' | 'en' | 'es'; + export type GetHealthData = { body?: never; headers?: { @@ -153,6 +229,201 @@ export type UpdateAccountCapabilitiesResponses = { export type UpdateAccountCapabilitiesResponse = UpdateAccountCapabilitiesResponses[keyof UpdateAccountCapabilitiesResponses]; +export type ListServiceCategoriesData = { + body?: never; + headers?: { + /** + * Optional client-supplied correlation identifier. + */ + 'X-Request-ID'?: RequestId; + }; + path?: never; + query: { + locale: 'pt-PT' | 'en' | 'es'; + }; + url: '/api/v1/catalog/categories'; +}; + +export type ListServiceCategoriesErrors = { + /** + * The request is invalid. + */ + 400: ErrorResponse; + /** + * A required dependency is unavailable. + */ + 503: ErrorResponse; +}; + +export type ListServiceCategoriesError = ListServiceCategoriesErrors[keyof ListServiceCategoriesErrors]; + +export type ListServiceCategoriesResponses = { + /** + * Active localized service categories. + */ + 200: CategoriesResponse; +}; + +export type ListServiceCategoriesResponse = ListServiceCategoriesResponses[keyof ListServiceCategoriesResponses]; + +export type ListSpokenLanguagesData = { + body?: never; + headers?: { + /** + * Optional client-supplied correlation identifier. + */ + 'X-Request-ID'?: RequestId; + }; + path?: never; + query: { + locale: 'pt-PT' | 'en' | 'es'; + }; + url: '/api/v1/reference/languages'; +}; + +export type ListSpokenLanguagesErrors = { + /** + * The request is invalid. + */ + 400: ErrorResponse; + /** + * A required dependency is unavailable. + */ + 503: ErrorResponse; +}; + +export type ListSpokenLanguagesError = ListSpokenLanguagesErrors[keyof ListSpokenLanguagesErrors]; + +export type ListSpokenLanguagesResponses = { + /** + * Active localized spoken languages. + */ + 200: LanguagesResponse; +}; + +export type ListSpokenLanguagesResponse = ListSpokenLanguagesResponses[keyof ListSpokenLanguagesResponses]; + +export type ListLocalitiesData = { + body?: never; + headers?: { + /** + * Optional client-supplied correlation identifier. + */ + 'X-Request-ID'?: RequestId; + }; + path?: never; + query: { + locale: 'pt-PT' | 'en' | 'es'; + nearLocalityId?: string; + radiusKm?: number; + }; + url: '/api/v1/reference/localities'; +}; + +export type ListLocalitiesErrors = { + /** + * The request is invalid. + */ + 400: ErrorResponse; + /** + * A required dependency is unavailable. + */ + 503: ErrorResponse; +}; + +export type ListLocalitiesError = ListLocalitiesErrors[keyof ListLocalitiesErrors]; + +export type ListLocalitiesResponses = { + /** + * Active locality references with required attribution. + */ + 200: LocalitiesResponse; +}; + +export type ListLocalitiesResponse = ListLocalitiesResponses[keyof ListLocalitiesResponses]; + +export type GetProviderProfileData = { + body?: never; + headers?: { + /** + * Optional client-supplied correlation identifier. + */ + 'X-Request-ID'?: RequestId; + }; + path?: never; + query?: never; + url: '/api/v1/me/provider-profile'; +}; + +export type GetProviderProfileErrors = { + /** + * Session authorization is missing or invalid. + */ + 401: ErrorResponse; + /** + * Provider capability is required. + */ + 403: ErrorResponse; + /** + * A required dependency is unavailable. + */ + 503: ErrorResponse; +}; + +export type GetProviderProfileError = GetProviderProfileErrors[keyof GetProviderProfileErrors]; + +export type GetProviderProfileResponses = { + /** + * Nullable owner-only provider profile. + */ + 200: ProviderProfileEnvelope; +}; + +export type GetProviderProfileResponse = GetProviderProfileResponses[keyof GetProviderProfileResponses]; + +export type ReplaceProviderProfileData = { + body: ReplaceProviderProfileRequest; + headers?: { + /** + * Optional client-supplied correlation identifier. + */ + 'X-Request-ID'?: RequestId; + }; + path?: never; + query?: never; + url: '/api/v1/me/provider-profile'; +}; + +export type ReplaceProviderProfileErrors = { + /** + * The request is invalid. + */ + 400: ErrorResponse; + /** + * Session authorization is missing or invalid. + */ + 401: ErrorResponse; + /** + * Provider capability is required. + */ + 403: ErrorResponse; + /** + * A required dependency is unavailable. + */ + 503: ErrorResponse; +}; + +export type ReplaceProviderProfileError = ReplaceProviderProfileErrors[keyof ReplaceProviderProfileErrors]; + +export type ReplaceProviderProfileResponses = { + /** + * Canonical owner-only provider profile. + */ + 200: ProviderProfileEnvelope; +}; + +export type ReplaceProviderProfileResponse = ReplaceProviderProfileResponses[keyof ReplaceProviderProfileResponses]; + export type ReconcileInternalUserData = { body?: never; headers?: { diff --git a/openapi/juntly-api.v1.yaml b/openapi/juntly-api.v1.yaml index 285be71..cad838e 100644 --- a/openapi/juntly-api.v1.yaml +++ b/openapi/juntly-api.v1.yaml @@ -119,6 +119,137 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorResponse" + /api/v1/catalog/categories: + get: + operationId: listServiceCategories + summary: List active service categories in one supported locale. + parameters: + - $ref: "#/components/parameters/RequestIdHeader" + - $ref: "#/components/parameters/LocaleQuery" + responses: + "200": + description: Active localized service categories. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/CategoriesResponse" + "400": + $ref: "#/components/responses/InvalidRequest" + "503": + $ref: "#/components/responses/ServiceUnavailable" + /api/v1/reference/languages: + get: + operationId: listSpokenLanguages + summary: List active spoken languages in one supported locale. + parameters: + - $ref: "#/components/parameters/RequestIdHeader" + - $ref: "#/components/parameters/LocaleQuery" + responses: + "200": + description: Active localized spoken languages. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/LanguagesResponse" + "400": + $ref: "#/components/responses/InvalidRequest" + "503": + $ref: "#/components/responses/ServiceUnavailable" + /api/v1/reference/localities: + get: + operationId: listLocalities + summary: List active launch localities, optionally within a radius. + parameters: + - $ref: "#/components/parameters/RequestIdHeader" + - $ref: "#/components/parameters/LocaleQuery" + - name: nearLocalityId + in: query + required: false + schema: + type: string + format: uuid + - name: radiusKm + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 200 + responses: + "200": + description: Active locality references with required attribution. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/LocalitiesResponse" + "400": + $ref: "#/components/responses/InvalidRequest" + "503": + $ref: "#/components/responses/ServiceUnavailable" + /api/v1/me/provider-profile: + get: + operationId: getProviderProfile + summary: Read the current provider's owner-only profile. + security: + - clerkSession: [] + parameters: + - $ref: "#/components/parameters/RequestIdHeader" + responses: + "200": + description: Nullable owner-only provider profile. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ProviderProfileEnvelope" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "503": + $ref: "#/components/responses/ServiceUnavailable" + put: + operationId: replaceProviderProfile + summary: Create or fully replace the current provider's owner-only profile. + security: + - clerkSession: [] + parameters: + - $ref: "#/components/parameters/RequestIdHeader" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ReplaceProviderProfileRequest" + responses: + "200": + description: Canonical owner-only provider profile. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ProviderProfileEnvelope" + "400": + $ref: "#/components/responses/InvalidRequest" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "503": + $ref: "#/components/responses/ServiceUnavailable" /api/v1/auth/reconcile: post: operationId: reconcileInternalUser @@ -170,11 +301,55 @@ components: description: Optional client-supplied correlation identifier. schema: $ref: "#/components/schemas/RequestId" + LocaleQuery: + name: locale + in: query + required: true + schema: + type: string + enum: [pt-PT, en, es] headers: RequestIdHeader: description: Correlation identifier attached to this response. schema: $ref: "#/components/schemas/RequestId" + responses: + InvalidRequest: + description: The request is invalid. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + Unauthorized: + description: Session authorization is missing or invalid. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + Forbidden: + description: Provider capability is required. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + ServiceUnavailable: + description: A required dependency is unavailable. + headers: + X-Request-ID: + $ref: "#/components/headers/RequestIdHeader" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" schemas: RequestId: type: string @@ -217,6 +392,7 @@ components: enum: - INVALID_REQUEST - UNAUTHORIZED + - FORBIDDEN - SERVICE_UNAVAILABLE ErrorDetail: type: object @@ -278,3 +454,132 @@ components: properties: providerEnabled: type: boolean + Category: + type: object + additionalProperties: false + required: [id, parentId, slug, name] + properties: + id: { type: string, format: uuid } + parentId: { type: [string, "null"], format: uuid } + slug: { type: string, minLength: 1, maxLength: 80 } + name: { type: string, minLength: 1, maxLength: 120 } + CategoriesResponse: + type: object + additionalProperties: false + required: [categories] + properties: + categories: + type: array + items: { $ref: "#/components/schemas/Category" } + SpokenLanguage: + type: object + additionalProperties: false + required: [code, name] + properties: + code: { type: string, minLength: 2, maxLength: 10 } + name: { type: string, minLength: 1, maxLength: 80 } + LanguagesResponse: + type: object + additionalProperties: false + required: [languages] + properties: + languages: + type: array + items: { $ref: "#/components/schemas/SpokenLanguage" } + Attribution: + type: object + additionalProperties: false + required: [text, url] + properties: + text: + type: string + const: © OpenStreetMap contributors + url: + type: string + format: uri + const: https://www.openstreetmap.org/copyright + Locality: + type: object + additionalProperties: false + required: [id, slug, name, parishName, municipalityName, districtName] + properties: + id: { type: string, format: uuid } + slug: { type: string, minLength: 1, maxLength: 100 } + name: { type: string, minLength: 1, maxLength: 160 } + parishName: { type: string, minLength: 1 } + municipalityName: { type: string, minLength: 1 } + districtName: { type: string, minLength: 1 } + distanceMeters: { type: integer, minimum: 0 } + LocalitiesResponse: + type: object + additionalProperties: false + required: [localities, attribution] + properties: + localities: + type: array + items: { $ref: "#/components/schemas/Locality" } + attribution: { $ref: "#/components/schemas/Attribution" } + ProviderType: + type: string + enum: [individual, professional, business] + ReplaceProviderProfileRequest: + type: object + additionalProperties: false + required: [displayName, providerType, bio, primaryLocalityId, serviceLocalityIds, maxTravelDistanceKm, travelsToCustomer, receivesCustomer, remoteServices, languageCodes] + properties: + displayName: { type: string, minLength: 2, maxLength: 100 } + providerType: { $ref: "#/components/schemas/ProviderType" } + bio: { type: string, maxLength: 1000 } + primaryLocalityId: { type: string, format: uuid } + serviceLocalityIds: + type: array + minItems: 1 + maxItems: 20 + uniqueItems: true + items: { type: string, format: uuid } + maxTravelDistanceKm: { type: integer, minimum: 0, maximum: 200 } + travelsToCustomer: { type: boolean } + receivesCustomer: { type: boolean } + remoteServices: { type: boolean } + languageCodes: + type: array + minItems: 1 + maxItems: 10 + uniqueItems: true + items: { type: string, minLength: 2, maxLength: 10 } + ProviderProfileResponse: + type: object + additionalProperties: false + required: [displayName, providerType, bio, primaryLocalityId, serviceLocalityIds, maxTravelDistanceKm, travelsToCustomer, receivesCustomer, remoteServices, languageCodes, createdAt, updatedAt] + properties: + displayName: { type: string, minLength: 2, maxLength: 100 } + providerType: { $ref: "#/components/schemas/ProviderType" } + bio: { type: string, maxLength: 1000 } + primaryLocalityId: { type: string, format: uuid } + serviceLocalityIds: + type: array + minItems: 1 + maxItems: 20 + uniqueItems: true + items: { type: string, format: uuid } + maxTravelDistanceKm: { type: integer, minimum: 0, maximum: 200 } + travelsToCustomer: { type: boolean } + receivesCustomer: { type: boolean } + remoteServices: { type: boolean } + languageCodes: + type: array + minItems: 1 + maxItems: 10 + uniqueItems: true + items: { type: string, minLength: 2, maxLength: 10 } + createdAt: { type: string, format: date-time } + updatedAt: { type: string, format: date-time } + ProviderProfileEnvelope: + type: object + additionalProperties: false + required: [profile] + properties: + profile: + oneOf: + - { $ref: "#/components/schemas/ProviderProfileResponse" } + - { type: "null" } From 9d23caa52dc40f4b51984f19df485d62dc61e8e6 Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 17:52:51 +0100 Subject: [PATCH 19/64] feat: add provider profile BFF --- .../api/v1/catalog/categories/route.test.ts | 84 ++++++ .../app/api/v1/catalog/categories/route.ts | 102 +++++++ .../api/v1/me/provider-profile/route.test.ts | 138 ++++++++++ .../app/api/v1/me/provider-profile/route.ts | 252 ++++++++++++++++++ .../api/v1/reference/languages/route.test.ts | 50 ++++ .../app/api/v1/reference/languages/route.ts | 75 ++++++ .../api/v1/reference/localities/route.test.ts | 62 +++++ .../app/api/v1/reference/localities/route.ts | 122 +++++++++ 8 files changed, 885 insertions(+) create mode 100644 frontend/src/app/api/v1/catalog/categories/route.test.ts create mode 100644 frontend/src/app/api/v1/catalog/categories/route.ts create mode 100644 frontend/src/app/api/v1/me/provider-profile/route.test.ts create mode 100644 frontend/src/app/api/v1/me/provider-profile/route.ts create mode 100644 frontend/src/app/api/v1/reference/languages/route.test.ts create mode 100644 frontend/src/app/api/v1/reference/languages/route.ts create mode 100644 frontend/src/app/api/v1/reference/localities/route.test.ts create mode 100644 frontend/src/app/api/v1/reference/localities/route.ts diff --git a/frontend/src/app/api/v1/catalog/categories/route.test.ts b/frontend/src/app/api/v1/catalog/categories/route.test.ts new file mode 100644 index 0000000..f57714a --- /dev/null +++ b/frontend/src/app/api/v1/catalog/categories/route.test.ts @@ -0,0 +1,84 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { GET } from "./route"; + +describe("GET /api/v1/catalog/categories", () => { + beforeEach(() => vi.stubEnv("JUNTLY_API_ORIGIN", "http://go-api:8080")); + afterEach(() => { + vi.unstubAllEnvs(); + vi.restoreAllMocks(); + }); + + it("forwards only locale and correlation through the generated client", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async (request: Request) => { + expect(request.url).toBe( + "http://go-api:8080/api/v1/catalog/categories?locale=pt-PT", + ); + expect(request.headers.get("Authorization")).toBeNull(); + expect(request.headers.get("X-Request-ID")).toBe("req_categories_ok"); + return Response.json( + { + categories: [ + { + id: "11111111-1111-4111-8111-111111111111", + parentId: null, + slug: "home-repairs", + name: "Reparações domésticas", + }, + ], + }, + { headers: { "X-Request-ID": "req_categories_ok" } }, + ); + }), + ); + + const response = await GET( + new Request("http://localhost/api/v1/catalog/categories?locale=pt-PT", { + headers: { "X-Request-ID": "req_categories_ok" }, + }), + ); + + expect(response.status).toBe(200); + await expect(response.json()).resolves.toMatchObject({ + categories: [{ slug: "home-repairs" }], + }); + }); + + it.each([ + "http://localhost/api/v1/catalog/categories", + "http://localhost/api/v1/catalog/categories?locale=fr", + "http://localhost/api/v1/catalog/categories?locale=pt-PT&admin=true", + "http://localhost/api/v1/catalog/categories?locale=pt-PT&locale=en", + ])("rejects invalid query %s before upstream", async (url) => { + const upstream = vi.fn(); + vi.stubGlobal("fetch", upstream); + const response = await GET( + new Request(url, { headers: { "X-Request-ID": "req_categories_bad" } }), + ); + expect(response.status).toBe(400); + expect(upstream).not.toHaveBeenCalled(); + }); + + it("maps malformed or mismatched upstream output to safe 503", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => + Response.json( + { categories: [], internalUserId: "private" }, + { headers: { "X-Request-ID": "different" } }, + ), + ), + ); + const response = await GET( + new Request("http://localhost/api/v1/catalog/categories?locale=en", { + headers: { "X-Request-ID": "req_categories_fail" }, + }), + ); + expect(response.status).toBe(503); + const body = await response.json(); + expect(JSON.stringify(body)).not.toContain("go-api"); + expect(JSON.stringify(body)).not.toContain("internalUserId"); + }); +}); diff --git a/frontend/src/app/api/v1/catalog/categories/route.ts b/frontend/src/app/api/v1/catalog/categories/route.ts new file mode 100644 index 0000000..9d9d22d --- /dev/null +++ b/frontend/src/app/api/v1/catalog/categories/route.ts @@ -0,0 +1,102 @@ +import { listServiceCategories } from "@/shared/api/generated"; +import type { CategoriesResponse, ErrorResponse } from "@/shared/api/generated"; + +const requestIDHeader = "X-Request-ID"; +export const runtime = "nodejs"; + +export async function GET(request: Request): Promise { + const requestID = readRequestID(request.headers); + const query = new URL(request.url).searchParams; + const locales = query.getAll("locale"); + if (query.size !== 1 || locales.length !== 1 || !isLocale(locales[0])) { + return errorResponse("INVALID_REQUEST", "Invalid request", 400, requestID); + } + const origin = process.env.JUNTLY_API_ORIGIN; + if (!origin) return unavailable(requestID); + try { + const upstream = await listServiceCategories({ + baseUrl: origin, + query: { locale: locales[0] }, + headers: { [requestIDHeader]: requestID }, + }); + if ( + upstream.error || + !upstream.response?.ok || + upstream.response.headers.get(requestIDHeader) !== requestID || + !isCategoriesResponse(upstream.data) + ) + return unavailable(requestID); + return Response.json(upstream.data, { + status: 200, + headers: { [requestIDHeader]: requestID }, + }); + } catch { + return unavailable(requestID); + } +} + +function isCategoriesResponse( + value: CategoriesResponse | undefined, +): value is CategoriesResponse { + return ( + isExact(value, ["categories"]) && + Array.isArray(value.categories) && + value.categories.every( + (category) => + isExact(category, ["id", "name", "parentId", "slug"]) && + isUUID(category.id) && + (category.parentId === null || isUUID(category.parentId)) && + typeof category.slug === "string" && + category.slug.length > 0 && + typeof category.name === "string" && + category.name.length > 0, + ) + ); +} + +function isExact( + value: unknown, + expected: string[], +): value is Record { + if (value === null || typeof value !== "object" || Array.isArray(value)) + return false; + const keys = Object.keys(value).sort(); + const wanted = [...expected].sort(); + return ( + keys.length === wanted.length && + keys.every((key, index) => key === wanted[index]) + ); +} +function isLocale(value: string | undefined): value is "pt-PT" | "en" | "es" { + return value === "pt-PT" || value === "en" || value === "es"; +} +function isUUID(value: string): boolean { + return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( + value, + ); +} +function readRequestID(headers: Headers): string { + const value = headers.get(requestIDHeader); + return value && /^[A-Za-z0-9._:-]{8,128}$/.test(value) + ? value + : `req_${crypto.randomUUID()}`; +} +function unavailable(requestID: string): Response { + return errorResponse( + "SERVICE_UNAVAILABLE", + "Service unavailable", + 503, + requestID, + ); +} +function errorResponse( + code: ErrorResponse["error"]["code"], + message: string, + status: number, + requestID: string, +): Response { + return Response.json( + { error: { code, message, requestId: requestID } } satisfies ErrorResponse, + { status, headers: { [requestIDHeader]: requestID } }, + ); +} diff --git a/frontend/src/app/api/v1/me/provider-profile/route.test.ts b/frontend/src/app/api/v1/me/provider-profile/route.test.ts new file mode 100644 index 0000000..231bf84 --- /dev/null +++ b/frontend/src/app/api/v1/me/provider-profile/route.test.ts @@ -0,0 +1,138 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ auth: vi.fn() })); +vi.mock("@clerk/nextjs/server", () => ({ auth: mocks.auth })); + +import { GET, PUT } from "./route"; + +const profile = { + displayName: "Prestador local", + providerType: "individual" as const, + bio: "Serviço de confiança.", + primaryLocalityId: "11111111-1111-4111-8111-111111111111", + serviceLocalityIds: ["11111111-1111-4111-8111-111111111111"], + maxTravelDistanceKm: 25, + travelsToCustomer: true, + receivesCustomer: false, + remoteServices: false, + languageCodes: ["pt-PT"], +}; + +describe("/api/v1/me/provider-profile", () => { + beforeEach(() => vi.stubEnv("JUNTLY_API_ORIGIN", "http://go-api:8080")); + afterEach(() => { + mocks.auth.mockReset(); + vi.unstubAllEnvs(); + vi.restoreAllMocks(); + }); + + it("returns 401 signed out without upstream", async () => { + mocks.auth.mockResolvedValue({ isAuthenticated: false, getToken: vi.fn() }); + const upstream = vi.fn(); + vi.stubGlobal("fetch", upstream); + const response = await GET( + new Request("http://localhost/api/v1/me/provider-profile", { + headers: { "X-Request-ID": "req_profile_signed_out" }, + }), + ); + expect(response.status).toBe(401); + expect(upstream).not.toHaveBeenCalled(); + }); + + it("forwards server bearer and validates owner-only GET response", async () => { + mocks.auth.mockResolvedValue({ + isAuthenticated: true, + getToken: vi.fn().mockResolvedValue("server-token"), + }); + vi.stubGlobal( + "fetch", + vi.fn(async (request: Request) => { + expect(request.headers.get("Authorization")).toBe( + "Bearer server-token", + ); + return Response.json( + { + profile: { + ...profile, + createdAt: "2026-08-23T16:00:00Z", + updatedAt: "2026-08-23T16:00:00Z", + }, + }, + { headers: { "X-Request-ID": "req_profile_get" } }, + ); + }), + ); + const response = await GET( + new Request("http://localhost/api/v1/me/provider-profile", { + headers: { "X-Request-ID": "req_profile_get" }, + }), + ); + expect(response.status).toBe(200); + await expect(response.json()).resolves.toMatchObject({ + profile: { providerType: "individual" }, + }); + }); + + it("PUT rejects unknown or incomplete browser data before upstream", async () => { + mocks.auth.mockResolvedValue({ + isAuthenticated: true, + getToken: vi.fn().mockResolvedValue("server-token"), + }); + for (const body of [{}, { ...profile, admin: true }]) { + const upstream = vi.fn(); + vi.stubGlobal("fetch", upstream); + const response = await PUT( + new Request("http://localhost/api/v1/me/provider-profile", { + method: "PUT", + headers: { + "Content-Type": "application/json", + "X-Request-ID": "req_profile_invalid", + }, + body: JSON.stringify(body), + }), + ); + expect(response.status).toBe(400); + expect(upstream).not.toHaveBeenCalled(); + } + }); + + it("PUT forwards exact validated profile and preserves 403", async () => { + mocks.auth.mockResolvedValue({ + isAuthenticated: true, + getToken: vi.fn().mockResolvedValue("server-token"), + }); + vi.stubGlobal( + "fetch", + vi.fn(async (request: Request) => { + await expect(request.json()).resolves.toEqual(profile); + return Response.json( + { + error: { + code: "FORBIDDEN", + message: "Forbidden", + requestId: "req_profile_forbidden", + }, + }, + { + status: 403, + headers: { "X-Request-ID": "req_profile_forbidden" }, + }, + ); + }), + ); + const response = await PUT( + new Request("http://localhost/api/v1/me/provider-profile", { + method: "PUT", + headers: { + "Content-Type": "application/json", + "X-Request-ID": "req_profile_forbidden", + }, + body: JSON.stringify(profile), + }), + ); + expect(response.status).toBe(403); + await expect(response.json()).resolves.toMatchObject({ + error: { code: "FORBIDDEN" }, + }); + }); +}); diff --git a/frontend/src/app/api/v1/me/provider-profile/route.ts b/frontend/src/app/api/v1/me/provider-profile/route.ts new file mode 100644 index 0000000..635510a --- /dev/null +++ b/frontend/src/app/api/v1/me/provider-profile/route.ts @@ -0,0 +1,252 @@ +import { auth } from "@clerk/nextjs/server"; + +import { + getProviderProfile, + replaceProviderProfile, +} from "@/shared/api/generated"; +import type { + ErrorResponse, + ProviderProfileEnvelope, + ReplaceProviderProfileRequest, +} from "@/shared/api/generated"; + +const requestIDHeader = "X-Request-ID"; +export const runtime = "nodejs"; + +export async function GET(request: Request): Promise { + const requestID = readRequestID(request.headers); + const token = await sessionToken(); + if (!token) + return errorResponse("UNAUTHORIZED", "Unauthorized", 401, requestID); + const origin = process.env.JUNTLY_API_ORIGIN; + if (!origin) return unavailable(requestID); + try { + const upstream = await getProviderProfile({ + baseUrl: origin, + headers: upstreamHeaders(token, requestID), + }); + return profileUpstreamResponse(upstream, requestID); + } catch { + return unavailable(requestID); + } +} + +export async function PUT(request: Request): Promise { + const requestID = readRequestID(request.headers); + const token = await sessionToken(); + if (!token) + return errorResponse("UNAUTHORIZED", "Unauthorized", 401, requestID); + const body = await readReplacement(request); + if (!body) + return errorResponse("INVALID_REQUEST", "Invalid request", 400, requestID); + const origin = process.env.JUNTLY_API_ORIGIN; + if (!origin) return unavailable(requestID); + try { + const upstream = await replaceProviderProfile({ + baseUrl: origin, + body, + headers: upstreamHeaders(token, requestID), + }); + return profileUpstreamResponse(upstream, requestID); + } catch { + return unavailable(requestID); + } +} + +async function sessionToken(): Promise { + try { + const state = await auth(); + return state.isAuthenticated ? await state.getToken() : null; + } catch { + return null; + } +} + +async function readReplacement( + request: Request, +): Promise { + try { + const value: unknown = JSON.parse(await request.text()); + if (!isProfileFields(value, false)) return null; + return value as ReplaceProviderProfileRequest; + } catch { + return null; + } +} + +function profileUpstreamResponse( + upstream: { + data?: ProviderProfileEnvelope; + error?: unknown; + response?: Response; + }, + requestID: string, +): Response { + if ( + upstream.response?.status === 403 && + isError(upstream.error, "FORBIDDEN", requestID) && + upstream.response.headers.get(requestIDHeader) === requestID + ) { + return Response.json(upstream.error, { + status: 403, + headers: { [requestIDHeader]: requestID }, + }); + } + if ( + upstream.error || + !upstream.response?.ok || + upstream.response.headers.get(requestIDHeader) !== requestID || + !isEnvelope(upstream.data) + ) + return unavailable(requestID); + return Response.json(upstream.data, { + status: 200, + headers: { [requestIDHeader]: requestID }, + }); +} + +function isEnvelope( + value: ProviderProfileEnvelope | undefined, +): value is ProviderProfileEnvelope { + return ( + isExact(value, ["profile"]) && + (value.profile === null || isProfileFields(value.profile, true)) + ); +} + +function isProfileFields( + value: unknown, + timestamps: boolean, +): value is Record { + const base = [ + "bio", + "displayName", + "languageCodes", + "maxTravelDistanceKm", + "primaryLocalityId", + "providerType", + "receivesCustomer", + "remoteServices", + "serviceLocalityIds", + "travelsToCustomer", + ]; + const keys = timestamps ? [...base, "createdAt", "updatedAt"] : base; + if (!isExact(value, keys)) return false; + const v = value as Record; + if ( + typeof v.displayName !== "string" || + v.displayName.trim().length < 2 || + v.displayName.length > 100 || + !["individual", "professional", "business"].includes( + String(v.providerType), + ) || + typeof v.bio !== "string" || + v.bio.length > 1000 || + typeof v.primaryLocalityId !== "string" || + !isUUID(v.primaryLocalityId) || + !uuidArray(v.serviceLocalityIds, 1, 20) || + !Number.isInteger(v.maxTravelDistanceKm) || + Number(v.maxTravelDistanceKm) < 0 || + Number(v.maxTravelDistanceKm) > 200 || + typeof v.travelsToCustomer !== "boolean" || + typeof v.receivesCustomer !== "boolean" || + typeof v.remoteServices !== "boolean" || + !stringArray(v.languageCodes, 1, 10) + ) + return false; + if (!v.travelsToCustomer && !v.receivesCustomer && !v.remoteServices) + return false; + if ( + timestamps && + (typeof v.createdAt !== "string" || + Number.isNaN(Date.parse(v.createdAt)) || + typeof v.updatedAt !== "string" || + Number.isNaN(Date.parse(v.updatedAt))) + ) + return false; + return true; +} + +function uuidArray(value: unknown, min: number, max: number): boolean { + return ( + Array.isArray(value) && + value.length >= min && + value.length <= max && + new Set(value).size === value.length && + value.every((item) => typeof item === "string" && isUUID(item)) + ); +} +function stringArray(value: unknown, min: number, max: number): boolean { + return ( + Array.isArray(value) && + value.length >= min && + value.length <= max && + new Set(value).size === value.length && + value.every( + (item) => + typeof item === "string" && item.length >= 2 && item.length <= 10, + ) + ); +} +function isError( + value: unknown, + code: string, + requestID: string, +): value is ErrorResponse { + return ( + isExact(value, ["error"]) && + isExact((value as { error: unknown }).error, [ + "code", + "message", + "requestId", + ]) && + (value as ErrorResponse).error.code === code && + (value as ErrorResponse).error.requestId === requestID + ); +} +function isExact( + value: unknown, + expected: string[], +): value is Record { + if (value === null || typeof value !== "object" || Array.isArray(value)) + return false; + const keys = Object.keys(value).sort(), + wanted = [...expected].sort(); + return ( + keys.length === wanted.length && + keys.every((key, index) => key === wanted[index]) + ); +} +function isUUID(value: string): boolean { + return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( + value, + ); +} +function upstreamHeaders(token: string, requestID: string): HeadersInit { + return { Authorization: `Bearer ${token}`, [requestIDHeader]: requestID }; +} +function readRequestID(headers: Headers): string { + const value = headers.get(requestIDHeader); + return value && /^[A-Za-z0-9._:-]{8,128}$/.test(value) + ? value + : `req_${crypto.randomUUID()}`; +} +function unavailable(requestID: string): Response { + return errorResponse( + "SERVICE_UNAVAILABLE", + "Service unavailable", + 503, + requestID, + ); +} +function errorResponse( + code: ErrorResponse["error"]["code"], + message: string, + status: number, + requestID: string, +): Response { + return Response.json( + { error: { code, message, requestId: requestID } } satisfies ErrorResponse, + { status, headers: { [requestIDHeader]: requestID } }, + ); +} diff --git a/frontend/src/app/api/v1/reference/languages/route.test.ts b/frontend/src/app/api/v1/reference/languages/route.test.ts new file mode 100644 index 0000000..7b67f16 --- /dev/null +++ b/frontend/src/app/api/v1/reference/languages/route.test.ts @@ -0,0 +1,50 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { GET } from "./route"; + +describe("GET /api/v1/reference/languages", () => { + beforeEach(() => vi.stubEnv("JUNTLY_API_ORIGIN", "http://go-api:8080")); + afterEach(() => { + vi.unstubAllEnvs(); + vi.restoreAllMocks(); + }); + + it("returns exact localized languages without authorization", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async (request: Request) => { + expect(request.url).toBe( + "http://go-api:8080/api/v1/reference/languages?locale=en", + ); + expect(request.headers.get("Authorization")).toBeNull(); + return Response.json( + { languages: [{ code: "pt-PT", name: "Portuguese" }] }, + { headers: { "X-Request-ID": "req_languages_ok" } }, + ); + }), + ); + const response = await GET( + new Request("http://localhost/api/v1/reference/languages?locale=en", { + headers: { "X-Request-ID": "req_languages_ok" }, + }), + ); + expect(response.status).toBe(200); + await expect(response.json()).resolves.toEqual({ + languages: [{ code: "pt-PT", name: "Portuguese" }], + }); + }); + + it("rejects unsupported or expanded queries before upstream", async () => { + for (const url of [ + "http://localhost/api/v1/reference/languages?locale=fr", + "http://localhost/api/v1/reference/languages?locale=en&admin=true", + ]) { + const upstream = vi.fn(); + vi.stubGlobal("fetch", upstream); + const response = await GET( + new Request(url, { headers: { "X-Request-ID": "req_languages_bad" } }), + ); + expect(response.status).toBe(400); + expect(upstream).not.toHaveBeenCalled(); + } + }); +}); diff --git a/frontend/src/app/api/v1/reference/languages/route.ts b/frontend/src/app/api/v1/reference/languages/route.ts new file mode 100644 index 0000000..b084573 --- /dev/null +++ b/frontend/src/app/api/v1/reference/languages/route.ts @@ -0,0 +1,75 @@ +import { listSpokenLanguages } from "@/shared/api/generated"; +import type { ErrorResponse, LanguagesResponse } from "@/shared/api/generated"; +const header = "X-Request-ID"; +export const runtime = "nodejs"; +export async function GET(request: Request): Promise { + const id = requestID(request.headers), + q = new URL(request.url).searchParams, + values = q.getAll("locale"); + if (q.size !== 1 || values.length !== 1 || !locale(values[0])) + return error("INVALID_REQUEST", "Invalid request", 400, id); + const origin = process.env.JUNTLY_API_ORIGIN; + if (!origin) return unavailable(id); + try { + const up = await listSpokenLanguages({ + baseUrl: origin, + query: { locale: values[0] }, + headers: { [header]: id }, + }); + if ( + up.error || + !up.response?.ok || + up.response.headers.get(header) !== id || + !valid(up.data) + ) + return unavailable(id); + return Response.json(up.data, { status: 200, headers: { [header]: id } }); + } catch { + return unavailable(id); + } +} +function valid( + value: LanguagesResponse | undefined, +): value is LanguagesResponse { + return ( + exact(value, ["languages"]) && + Array.isArray(value.languages) && + value.languages.every( + (x) => + exact(x, ["code", "name"]) && + typeof x.code === "string" && + x.code.length >= 2 && + typeof x.name === "string" && + x.name.length > 0, + ) + ); +} +function exact(v: unknown, e: string[]): v is Record { + if (v === null || typeof v !== "object" || Array.isArray(v)) return false; + const k = Object.keys(v).sort(), + w = [...e].sort(); + return k.length === w.length && k.every((x, i) => x === w[i]); +} +function locale(v: string | undefined): v is "pt-PT" | "en" | "es" { + return v === "pt-PT" || v === "en" || v === "es"; +} +function requestID(h: Headers) { + const v = h.get(header); + return v && /^[A-Za-z0-9._:-]{8,128}$/.test(v) + ? v + : `req_${crypto.randomUUID()}`; +} +function unavailable(id: string) { + return error("SERVICE_UNAVAILABLE", "Service unavailable", 503, id); +} +function error( + code: ErrorResponse["error"]["code"], + message: string, + status: number, + id: string, +) { + return Response.json( + { error: { code, message, requestId: id } } satisfies ErrorResponse, + { status, headers: { [header]: id } }, + ); +} diff --git a/frontend/src/app/api/v1/reference/localities/route.test.ts b/frontend/src/app/api/v1/reference/localities/route.test.ts new file mode 100644 index 0000000..d067c90 --- /dev/null +++ b/frontend/src/app/api/v1/reference/localities/route.test.ts @@ -0,0 +1,62 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { GET } from "./route"; +describe("GET /api/v1/reference/localities", () => { + beforeEach(() => vi.stubEnv("JUNTLY_API_ORIGIN", "http://go-api:8080")); + afterEach(() => { + vi.unstubAllEnvs(); + vi.restoreAllMocks(); + }); + it("forwards paired radius and preserves attribution", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async (request: Request) => { + expect(request.url).toContain("locale=pt-PT"); + expect(request.url).toContain("radiusKm=25"); + return Response.json( + { + localities: [ + { + id: "11111111-1111-4111-8111-111111111111", + slug: "zebreira", + name: "Zebreira", + parishName: "Zebreira e Segura", + municipalityName: "Idanha-a-Nova", + districtName: "Castelo Branco", + distanceMeters: 0, + }, + ], + attribution: { + text: "© OpenStreetMap contributors", + url: "https://www.openstreetmap.org/copyright", + }, + }, + { headers: { "X-Request-ID": "req_localities_ok" } }, + ); + }), + ); + const r = await GET( + new Request( + "http://localhost/api/v1/reference/localities?locale=pt-PT&nearLocalityId=11111111-1111-4111-8111-111111111111&radiusKm=25", + { headers: { "X-Request-ID": "req_localities_ok" } }, + ), + ); + expect(r.status).toBe(200); + const b = await r.json(); + expect(b.attribution.text).toContain("OpenStreetMap"); + expect(JSON.stringify(b)).not.toContain("latitude"); + }); + it("rejects unpaired or unknown query", async () => { + for (const url of [ + "http://localhost/api/v1/reference/localities?locale=en&radiusKm=10", + "http://localhost/api/v1/reference/localities?locale=en&admin=true", + ]) { + const f = vi.fn(); + vi.stubGlobal("fetch", f); + const r = await GET( + new Request(url, { headers: { "X-Request-ID": "req_localities_bad" } }), + ); + expect(r.status).toBe(400); + expect(f).not.toHaveBeenCalled(); + } + }); +}); diff --git a/frontend/src/app/api/v1/reference/localities/route.ts b/frontend/src/app/api/v1/reference/localities/route.ts new file mode 100644 index 0000000..22f962f --- /dev/null +++ b/frontend/src/app/api/v1/reference/localities/route.ts @@ -0,0 +1,122 @@ +import { listLocalities } from "@/shared/api/generated"; +import type { ErrorResponse, LocalitiesResponse } from "@/shared/api/generated"; +const header = "X-Request-ID"; +export const runtime = "nodejs"; +export async function GET(request: Request): Promise { + const id = requestID(request.headers), + q = new URL(request.url).searchParams; + if (!keys(q, ["locale", "nearLocalityId", "radiusKm"])) + return error("INVALID_REQUEST", "Invalid request", 400, id); + const locales = q.getAll("locale"), + near = q.getAll("nearLocalityId"), + radius = q.getAll("radiusKm"); + if ( + locales.length !== 1 || + !locale(locales[0]) || + near.length > 1 || + radius.length > 1 || + (near.length === 1) !== (radius.length === 1) + ) + return error("INVALID_REQUEST", "Invalid request", 400, id); + const query: { + locale: "pt-PT" | "en" | "es"; + nearLocalityId?: string; + radiusKm?: number; + } = { locale: locales[0] }; + if (near.length) { + if ( + !uuid(near[0]) || + !/^(?:[1-9]|[1-9][0-9]|1[0-9]{2}|200)$/.test(radius[0]) + ) + return error("INVALID_REQUEST", "Invalid request", 400, id); + query.nearLocalityId = near[0]; + query.radiusKm = Number(radius[0]); + } + const origin = process.env.JUNTLY_API_ORIGIN; + if (!origin) return unavailable(id); + try { + const up = await listLocalities({ + baseUrl: origin, + query, + headers: { [header]: id }, + }); + if ( + up.error || + !up.response?.ok || + up.response.headers.get(header) !== id || + !valid(up.data) + ) + return unavailable(id); + return Response.json(up.data, { status: 200, headers: { [header]: id } }); + } catch { + return unavailable(id); + } +} +function valid(v: LocalitiesResponse | undefined): v is LocalitiesResponse { + return ( + exact(v, ["attribution", "localities"]) && + exact(v.attribution, ["text", "url"]) && + v.attribution.text === "© OpenStreetMap contributors" && + v.attribution.url === "https://www.openstreetmap.org/copyright" && + Array.isArray(v.localities) && + v.localities.every((x) => { + const expected = [ + "districtName", + "id", + "municipalityName", + "name", + "parishName", + "slug", + ...(x.distanceMeters === undefined ? [] : ["distanceMeters"]), + ]; + return ( + exact(x, expected) && + uuid(x.id) && + typeof x.slug === "string" && + typeof x.name === "string" && + typeof x.parishName === "string" && + typeof x.municipalityName === "string" && + typeof x.districtName === "string" && + (x.distanceMeters === undefined || + (Number.isInteger(x.distanceMeters) && x.distanceMeters >= 0)) + ); + }) + ); +} +function keys(q: URLSearchParams, allowed: string[]) { + return [...q.keys()].every((k) => allowed.includes(k)); +} +function exact(v: unknown, e: string[]): v is Record { + if (v === null || typeof v !== "object" || Array.isArray(v)) return false; + const k = Object.keys(v).sort(), + w = [...e].sort(); + return k.length === w.length && k.every((x, i) => x === w[i]); +} +function locale(v: string | undefined): v is "pt-PT" | "en" | "es" { + return v === "pt-PT" || v === "en" || v === "es"; +} +function uuid(v: string) { + return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( + v, + ); +} +function requestID(h: Headers) { + const v = h.get(header); + return v && /^[A-Za-z0-9._:-]{8,128}$/.test(v) + ? v + : `req_${crypto.randomUUID()}`; +} +function unavailable(id: string) { + return error("SERVICE_UNAVAILABLE", "Service unavailable", 503, id); +} +function error( + code: ErrorResponse["error"]["code"], + message: string, + status: number, + id: string, +) { + return Response.json( + { error: { code, message, requestId: id } } satisfies ErrorResponse, + { status, headers: { [header]: id } }, + ); +} From ea07305a3d1a9dfdeb46bede5153e8a30e44699e Mon Sep 17 00:00:00 2001 From: SourceSensei Date: Sun, 23 Aug 2026 18:08:55 +0100 Subject: [PATCH 20/64] feat: add provider profile onboarding --- frontend/messages/en.json | 26 +- frontend/messages/es.json | 26 +- frontend/messages/pt-PT.json | 26 +- .../src/app/[locale]/account/page.test.tsx | 1 + frontend/src/app/[locale]/account/page.tsx | 6 +- .../account/provider-profile/page.test.tsx | 33 ++ .../account/provider-profile/page.tsx | 53 +++ .../account-capabilities-card.test.tsx | 11 +- .../account/account-capabilities-card.tsx | 11 + .../provider/provider-profile-form.test.tsx | 154 +++++++ .../provider/provider-profile-form.tsx | 405 ++++++++++++++++++ 11 files changed, 747 insertions(+), 5 deletions(-) create mode 100644 frontend/src/app/[locale]/account/provider-profile/page.test.tsx create mode 100644 frontend/src/app/[locale]/account/provider-profile/page.tsx create mode 100644 frontend/src/features/provider/provider-profile-form.test.tsx create mode 100644 frontend/src/features/provider/provider-profile-form.tsx diff --git a/frontend/messages/en.json b/frontend/messages/en.json index e667251..52e907d 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -38,9 +38,33 @@ "loading": "Loading account capabilities…", "saving": "Saving…", "loadError": "We could not load your account capabilities.", - "retry": "Try again" + "retry": "Try again", + "manageProvider": "Manage provider profile" } }, + "ProviderProfile": { + "title": "Provider profile", + "description": "Prepare your private profile details before marketplace publication.", + "displayName": "Display name", + "providerType": "Provider type", + "individual": "Individual", + "professional": "Professional", + "business": "Business", + "bio": "Introduction", + "primaryLocality": "Primary locality", + "serviceLocalities": "Service areas", + "languages": "Languages", + "travelRadius": "Maximum travel distance in kilometres", + "travels": "Travels to the customer", + "receives": "Receives customers", + "remote": "Works remotely", + "save": "Save profile", + "saving": "Saving…", + "loading": "Loading profile…", + "error": "We could not load or save the profile.", + "retry": "Try again", + "saved": "Profile saved." + }, "Errors": { "title": "Something went wrong", "description": "We could not load this page. Please try again.", diff --git a/frontend/messages/es.json b/frontend/messages/es.json index 09c3e31..052296d 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -38,9 +38,33 @@ "loading": "Cargando las capacidades de la cuenta…", "saving": "Guardando…", "loadError": "No hemos podido cargar las capacidades de tu cuenta.", - "retry": "Intentar de nuevo" + "retry": "Intentar de nuevo", + "manageProvider": "Gestionar perfil profesional" } }, + "ProviderProfile": { + "title": "Perfil profesional", + "description": "Prepara los datos privados de tu perfil antes de publicarlo en el marketplace.", + "displayName": "Nombre público", + "providerType": "Tipo de profesional", + "individual": "Particular", + "professional": "Profesional", + "business": "Empresa", + "bio": "Presentación", + "primaryLocality": "Localidad principal", + "serviceLocalities": "Áreas de servicio", + "languages": "Idiomas", + "travelRadius": "Distancia máxima en kilómetros", + "travels": "Se desplaza al cliente", + "receives": "Recibe clientes", + "remote": "Trabaja a distancia", + "save": "Guardar perfil", + "saving": "Guardando…", + "loading": "Cargando perfil…", + "error": "No hemos podido cargar o guardar el perfil.", + "retry": "Intentar de nuevo", + "saved": "Perfil guardado." + }, "Errors": { "title": "Algo salió mal", "description": "No hemos podido cargar esta página. Inténtalo de nuevo.", diff --git a/frontend/messages/pt-PT.json b/frontend/messages/pt-PT.json index f79ae0c..adacc5b 100644 --- a/frontend/messages/pt-PT.json +++ b/frontend/messages/pt-PT.json @@ -38,9 +38,33 @@ "loading": "A carregar as capacidades da conta…", "saving": "A guardar…", "loadError": "Não foi possível carregar as capacidades da conta.", - "retry": "Tentar novamente" + "retry": "Tentar novamente", + "manageProvider": "Gerir perfil de prestador" } }, + "ProviderProfile": { + "title": "Perfil de prestador", + "description": "Prepare os dados privados do seu perfil antes da publicação no marketplace.", + "displayName": "Nome de apresentação", + "providerType": "Tipo de prestador", + "individual": "Particular", + "professional": "Profissional", + "business": "Empresa", + "bio": "Apresentação", + "primaryLocality": "Localidade principal", + "serviceLocalities": "Áreas de serviço", + "languages": "Idiomas", + "travelRadius": "Distância máxima em quilómetros", + "travels": "Desloca-se ao cliente", + "receives": "Recebe clientes", + "remote": "Trabalha à distância", + "save": "Guardar perfil", + "saving": "A guardar…", + "loading": "A carregar perfil…", + "error": "Não foi possível carregar ou guardar o perfil.", + "retry": "Tentar novamente", + "saved": "Perfil guardado." + }, "Errors": { "title": "Algo não correu bem", "description": "Não foi possível carregar esta página. Tente novamente.", diff --git a/frontend/src/app/[locale]/account/page.test.tsx b/frontend/src/app/[locale]/account/page.test.tsx index 3aebf3e..179d0b9 100644 --- a/frontend/src/app/[locale]/account/page.test.tsx +++ b/frontend/src/app/[locale]/account/page.test.tsx @@ -45,6 +45,7 @@ describe("AccountPage", () => { "capabilities.loadError": "Não foi possível carregar as capacidades da conta.", "capabilities.loading": "A carregar as capacidades da conta…", + "capabilities.manageProvider": "Gerir perfil de prestador", "capabilities.providerDescription": "Ative esta opção para preparar o seu perfil de prestador.", "capabilities.providerLabel": "Disponibilizar serviços", diff --git a/frontend/src/app/[locale]/account/page.tsx b/frontend/src/app/[locale]/account/page.tsx index 40315d8..17d24dd 100644 --- a/frontend/src/app/[locale]/account/page.tsx +++ b/frontend/src/app/[locale]/account/page.tsx @@ -34,6 +34,7 @@ export default async function AccountPage({ params }: AccountPageProps) { saving: t("capabilities.saving"), loadError: t("capabilities.loadError"), retry: t("capabilities.retry"), + manageProvider: t("capabilities.manageProvider"), }; return ( @@ -46,7 +47,10 @@ export default async function AccountPage({ params }: AccountPageProps) { {t("title")}

{t("description")}

- +
); diff --git a/frontend/src/app/[locale]/account/provider-profile/page.test.tsx b/frontend/src/app/[locale]/account/provider-profile/page.test.tsx new file mode 100644 index 0000000..01484d0 --- /dev/null +++ b/frontend/src/app/[locale]/account/provider-profile/page.test.tsx @@ -0,0 +1,33 @@ +import { render, screen } from "@testing-library/react"; +import { afterEach, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + getTranslations: vi.fn(), + requireAuthenticatedUser: vi.fn(), +})); +vi.mock("next-intl/server", () => ({ getTranslations: mocks.getTranslations })); +vi.mock("@/features/auth/require-session", () => ({ + requireAuthenticatedUser: mocks.requireAuthenticatedUser, +})); +vi.mock("@/features/provider/provider-profile-form", () => ({ + ProviderProfileForm: ({ locale }: { locale: string }) => ( +
{locale}
+ ), +})); +import ProviderProfilePage, { dynamic } from "./page"; +afterEach(() => { + mocks.getTranslations.mockReset(); + mocks.requireAuthenticatedUser.mockReset(); +}); +it("protects and renders the localized dynamic provider profile page", async () => { + mocks.requireAuthenticatedUser.mockResolvedValue("verified"); + mocks.getTranslations.mockResolvedValue((key: string) => key); + render( + await ProviderProfilePage({ + params: Promise.resolve({ locale: "pt-PT" }), + } as never), + ); + expect(dynamic).toBe("force-dynamic"); + expect(mocks.requireAuthenticatedUser).toHaveBeenCalledWith("pt-PT"); + expect(screen.getByTestId("provider-form")).toHaveTextContent("pt-PT"); +}); diff --git a/frontend/src/app/[locale]/account/provider-profile/page.tsx b/frontend/src/app/[locale]/account/provider-profile/page.tsx new file mode 100644 index 0000000..706f897 --- /dev/null +++ b/frontend/src/app/[locale]/account/provider-profile/page.tsx @@ -0,0 +1,53 @@ +import { hasLocale } from "next-intl"; +import { getTranslations } from "next-intl/server"; +import { notFound } from "next/navigation"; + +import { requireAuthenticatedUser } from "@/features/auth/require-session"; +import { ProviderProfileForm } from "@/features/provider/provider-profile-form"; +import { routing } from "@/i18n/routing"; + +export const dynamic = "force-dynamic"; +export default async function ProviderProfilePage({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + if (!hasLocale(routing.locales, locale)) notFound(); + await requireAuthenticatedUser(locale); + const t = await getTranslations("ProviderProfile"); + const keys = [ + "title", + "description", + "displayName", + "providerType", + "individual", + "professional", + "business", + "bio", + "primaryLocality", + "serviceLocalities", + "languages", + "travelRadius", + "travels", + "receives", + "remote", + "save", + "saving", + "loading", + "error", + "retry", + "saved", + ] as const; + const copy = Object.fromEntries(keys.map((key) => [key, t(key)])) as Record< + (typeof keys)[number], + string + >; + return ( +
+
+ +
+
+ ); +} diff --git a/frontend/src/features/account/account-capabilities-card.test.tsx b/frontend/src/features/account/account-capabilities-card.test.tsx index 9deb701..93f7f71 100644 --- a/frontend/src/features/account/account-capabilities-card.test.tsx +++ b/frontend/src/features/account/account-capabilities-card.test.tsx @@ -17,6 +17,7 @@ const copy = { saving: "A guardar…", loadError: "Não foi possível carregar as capacidades da conta.", retry: "Tentar novamente", + manageProvider: "Gerir perfil de prestador", }; afterEach(() => { @@ -54,7 +55,12 @@ describe("AccountCapabilitiesCard", () => { .mockImplementationOnce(() => update.promise); vi.stubGlobal("fetch", fetchMock); - render(); + render( + , + ); const toggle = await screen.findByRole("switch", { name: copy.providerLabel, }); @@ -74,6 +80,9 @@ describe("AccountCapabilitiesCard", () => { update.resolve(accountResponse(true)); await waitFor(() => expect(toggle).toHaveAttribute("aria-checked", "true")); expect(toggle).not.toBeDisabled(); + expect( + screen.getByRole("link", { name: copy.manageProvider }), + ).toHaveAttribute("href", "/pt-PT/account/provider-profile"); }); it("shows a controlled error and retries without exposing upstream details", async () => { diff --git a/frontend/src/features/account/account-capabilities-card.tsx b/frontend/src/features/account/account-capabilities-card.tsx index 44ceb74..24b862b 100644 --- a/frontend/src/features/account/account-capabilities-card.tsx +++ b/frontend/src/features/account/account-capabilities-card.tsx @@ -21,14 +21,17 @@ export type AccountCapabilitiesCopy = { saving: string; loadError: string; retry: string; + manageProvider: string; }; type AccountCapabilitiesCardProps = { copy: AccountCapabilitiesCopy; + providerProfileUrl?: string; }; export function AccountCapabilitiesCard({ copy, + providerProfileUrl, }: AccountCapabilitiesCardProps) { const [account, setAccount] = useState(null); const [loading, setLoading] = useState(true); @@ -200,6 +203,14 @@ export function AccountCapabilitiesCard({ ? copy.enabled : copy.disabled}

+ {account.providerEnabled && providerProfileUrl ? ( + + {copy.manageProvider} + + ) : null} + + ); + const update = (key: K, value: Draft[K]) => + setDraft((current) => (current ? { ...current, [key]: value } : current)); + const toggle = (values: string[], value: string) => + values.includes(value) + ? values.filter((item) => item !== value) + : [...values, value]; + return ( +
+

+ {copy.title} +

+

{copy.description}

+ {failed ? ( +

+ {copy.error} +

+ ) : null} + {saved ? ( +

+ {copy.saved} +

+ ) : null} +
+ + +