diff --git a/apps/email/Dockerfile b/apps/email/Dockerfile new file mode 100644 index 0000000000..03f42d134f --- /dev/null +++ b/apps/email/Dockerfile @@ -0,0 +1,31 @@ +FROM node:20-alpine AS base + +FROM base AS builder + +RUN apk add --no-cache libc6-compat + +WORKDIR /app + +COPY apps/email/package.json ./ +RUN npm install + +COPY apps/email/ ./ + +RUN npm run build + +FROM node:20-alpine AS runner + +WORKDIR /app + +RUN addgroup -S -g 1001 nodejs && adduser -S -u 1001 -G nodejs nodeuser +USER nodeuser + +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/node_modules ./node_modules + +ENV NODE_ENV=production +ENV PORT=3001 + +EXPOSE 3001 + +CMD ["node", "dist/index.js"] diff --git a/apps/email/package.json b/apps/email/package.json new file mode 100644 index 0000000000..ac2f708607 --- /dev/null +++ b/apps/email/package.json @@ -0,0 +1,32 @@ +{ + "name": "@echo-webkom/email-service", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "dotenv -e ../../.env -- tsx watch src/index.ts", + "email:dev": "react-email dev --port 3001", + "build": "tsdown src/index.ts --format cjs --out-dir dist", + "start": "node dist/index.js", + "check": "oxlint . && oxfmt --check . && tsc --noEmit", + "check:fix": "oxlint . --fix && oxfmt . && tsc --noEmit", + "clean": "rm -rf dist .turbo node_modules" + }, + "dependencies": { + "@hono/node-server": "^1.13.7", + "@react-email/components": "1.0.6", + "@react-email/render": "2.0.4", + "hono": "^4.7.10", + "react": "19.2.4", + "react-dom": "19.2.4", + "resend": "6.9.1" + }, + "devDependencies": { + "@types/node": "22.19.7", + "@types/react": "19.2.10", + "@types/react-dom": "19.2.3", + "dotenv-cli": "11.0.0", + "tsdown": "^0.21.7", + "tsx": "^4.19.4", + "typescript": "5.9.3" + } +} diff --git a/apps/email/src/email.ts b/apps/email/src/email.ts new file mode 100644 index 0000000000..7493b57be1 --- /dev/null +++ b/apps/email/src/email.ts @@ -0,0 +1,18 @@ +/* eslint-disable no-console */ +import { Resend } from "resend"; + +const FROM_EMAIL = "echo "; +const IS_PROD = process.env.ENVIRONMENT?.startsWith("p") && !!process.env.RESEND_API_KEY; + +export async function sendEmail(to: Array, subject: string, html: string): Promise { + if (!IS_PROD) { + console.log("\n========== EMAIL SENT (DEV MODE) =========="); + console.log("TO:", to.join(", ")); + console.log("SUBJECT:", subject); + console.log("==========================================\n"); + return; + } + + const resend = new Resend(process.env.RESEND_API_KEY); + await resend.emails.send({ from: FROM_EMAIL, to, subject, html }); +} diff --git a/packages/email/emails/access-denied.tsx b/apps/email/src/emails/access-denied.tsx similarity index 100% rename from packages/email/emails/access-denied.tsx rename to apps/email/src/emails/access-denied.tsx diff --git a/packages/email/emails/access-granted.tsx b/apps/email/src/emails/access-granted.tsx similarity index 100% rename from packages/email/emails/access-granted.tsx rename to apps/email/src/emails/access-granted.tsx diff --git a/packages/email/emails/access-request-notification.tsx b/apps/email/src/emails/access-request-notification.tsx similarity index 100% rename from packages/email/emails/access-request-notification.tsx rename to apps/email/src/emails/access-request-notification.tsx diff --git a/packages/email/emails/deregistration-notification.tsx b/apps/email/src/emails/deregistration-notification.tsx similarity index 100% rename from packages/email/emails/deregistration-notification.tsx rename to apps/email/src/emails/deregistration-notification.tsx diff --git a/packages/email/emails/email-verification.tsx b/apps/email/src/emails/email-verification.tsx similarity index 100% rename from packages/email/emails/email-verification.tsx rename to apps/email/src/emails/email-verification.tsx diff --git a/packages/email/emails/got-spot-notification.tsx b/apps/email/src/emails/got-spot-notification.tsx similarity index 100% rename from packages/email/emails/got-spot-notification.tsx rename to apps/email/src/emails/got-spot-notification.tsx diff --git a/packages/email/emails/magic-link.tsx b/apps/email/src/emails/magic-link.tsx similarity index 100% rename from packages/email/emails/magic-link.tsx rename to apps/email/src/emails/magic-link.tsx diff --git a/packages/email/emails/registration-confirmation.tsx b/apps/email/src/emails/registration-confirmation.tsx similarity index 100% rename from packages/email/emails/registration-confirmation.tsx rename to apps/email/src/emails/registration-confirmation.tsx diff --git a/packages/email/emails/strike-notification.tsx b/apps/email/src/emails/strike-notification.tsx similarity index 100% rename from packages/email/emails/strike-notification.tsx rename to apps/email/src/emails/strike-notification.tsx diff --git a/apps/email/src/index.ts b/apps/email/src/index.ts new file mode 100644 index 0000000000..e5d66df2b9 --- /dev/null +++ b/apps/email/src/index.ts @@ -0,0 +1,10 @@ +import { serve } from "@hono/node-server"; + +import { app } from "./routes"; + +const PORT = Number(process.env.EMAIL_PORT) || 6000; + +serve({ fetch: app.fetch, port: PORT }, () => { + // oxlint-disable-next-line no-console + console.log(`Email service running on http://localhost:${PORT}`); +}); diff --git a/apps/email/src/routes.ts b/apps/email/src/routes.ts new file mode 100644 index 0000000000..53870b5c5a --- /dev/null +++ b/apps/email/src/routes.ts @@ -0,0 +1,158 @@ +import { render } from "@react-email/render"; +import { Hono } from "hono"; +import { cors } from "hono/cors"; +import { logger } from "hono/logger"; +import * as React from "react"; + +import { sendEmail } from "./email"; +import AccessDeniedEmail from "./emails/access-denied"; +import AccessGrantedEmail from "./emails/access-granted"; +import AccessRequestNotificationEmail from "./emails/access-request-notification"; +import DeregistrationNotificationEmail from "./emails/deregistration-notification"; +import EmailVerificationEmail from "./emails/email-verification"; +import GotSpotNotificationEmail from "./emails/got-spot-notification"; +import MagicLinkEmail from "./emails/magic-link"; +import RegistrationConfirmationEmail from "./emails/registration-confirmation"; +import StrikeNotificationEmail from "./emails/strike-notification"; + +const app = new Hono(); + +app.use(logger()); +app.use(cors()); + +app.use("*", async (c, next) => { + // Allow unauthenticated access to the root path for health checks + if (c.req.path === "/") { + await next(); + } + // For all other paths, require authentication + // Check if the Authorization header is present and matches the expected admin key + const authHeader = c.req.header("Authorization"); + if (!authHeader || authHeader !== `Bearer ${process.env.ADMIN_KEY}`) { + return c.json({ error: "Unauthorized" }, 401); + } + await next(); +}); + +app.get("/", (c) => c.json({ status: "ok" })); + +app.post("/registration-confirmation", async (c) => { + const { to, subject, title, isBedpres } = await c.req.json<{ + to: Array; + subject: string; + title?: string; + isBedpres?: boolean; + }>(); + const html = await render( + React.createElement(RegistrationConfirmationEmail, { title, isBedpres }), + ); + await sendEmail(to, subject, html); + return c.json({ success: true }); +}); + +app.post("/deregistration-notification", async (c) => { + const { to, subject, name, reason, happeningTitle } = await c.req.json<{ + to: Array; + subject: string; + name?: string; + reason?: string; + happeningTitle?: string; + }>(); + const html = await render( + React.createElement(DeregistrationNotificationEmail, { name, reason, happeningTitle }), + ); + await sendEmail(to, subject, html); + return c.json({ success: true }); +}); + +app.post("/got-spot-notification", async (c) => { + const { to, subject, name, happeningTitle } = await c.req.json<{ + to: Array; + subject: string; + name?: string; + happeningTitle?: string; + }>(); + const html = await render( + React.createElement(GotSpotNotificationEmail, { name, happeningTitle }), + ); + await sendEmail(to, subject, html); + return c.json({ success: true }); +}); + +app.post("/strike-notification", async (c) => { + const { to, subject, name, reason, amount, isBanned } = await c.req.json<{ + to: Array; + subject: string; + name?: string; + reason?: string; + amount?: number; + isBanned?: boolean; + }>(); + const html = await render( + React.createElement(StrikeNotificationEmail, { name, reason, amount, isBanned }), + ); + await sendEmail(to, subject, html); + return c.json({ success: true }); +}); + +app.post("/access-granted", async (c) => { + const { to, subject } = await c.req.json<{ + to: Array; + subject: string; + }>(); + const html = await render(React.createElement(AccessGrantedEmail)); + await sendEmail(to, subject, html); + return c.json({ success: true }); +}); + +app.post("/access-denied", async (c) => { + const { to, subject, reason } = await c.req.json<{ + to: Array; + subject: string; + reason?: string; + }>(); + const html = await render(React.createElement(AccessDeniedEmail, { reason })); + await sendEmail(to, subject, html); + return c.json({ success: true }); +}); + +app.post("/access-request-notification", async (c) => { + const { to, subject, email, reason } = await c.req.json<{ + to: Array; + subject: string; + email?: string; + reason?: string; + }>(); + const html = await render(React.createElement(AccessRequestNotificationEmail, { email, reason })); + await sendEmail(to, subject, html); + return c.json({ success: true }); +}); + +app.post("/email-verification", async (c) => { + const { to, subject, verificationUrl, firstName } = await c.req.json<{ + to: Array; + subject: string; + verificationUrl: string; + firstName?: string; + }>(); + const html = await render( + React.createElement(EmailVerificationEmail, { verificationUrl, firstName }), + ); + await sendEmail(to, subject, html); + return c.json({ success: true }); +}); + +app.post("/magic-link", async (c) => { + const { to, subject, magicLinkUrl, code, firstName } = await c.req.json<{ + to: Array; + subject: string; + magicLinkUrl: string; + code: string; + firstName?: string; + }>(); + const html = await render(React.createElement(MagicLinkEmail, { magicLinkUrl, code, firstName })); + await sendEmail(to, subject, html); + return c.json({ success: true }); +}); + +export { app }; diff --git a/apps/email/tsconfig.json b/apps/email/tsconfig.json new file mode 100644 index 0000000000..57d100cdc9 --- /dev/null +++ b/apps/email/tsconfig.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "es2022", + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "jsx": "react-jsx", + "outDir": "dist" + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules", "dist"] +} diff --git a/apps/uno/bootstrap/api.go b/apps/uno/bootstrap/api.go index daa165b11e..87e4882cab 100644 --- a/apps/uno/bootstrap/api.go +++ b/apps/uno/bootstrap/api.go @@ -59,6 +59,7 @@ func RunApi() { weatherRepo := external.NewYrRepo(logger, redisClient) databrusRepo := external.NewDatabrusRepo(logger, redisClient) adventOfCodeRepo := external.NewAdventOfCodeClient(aocClient, logger, redisClient) + emailClient := external.NewEmailClient(cfg.EmailBaseURL) groupRepo := postgres.NewGroupRepo(db, logger) reactionRepo := postgres.NewReactionRepo(db, logger) quoteRepo := postgres.NewQuoteRepo(db, logger) @@ -105,13 +106,13 @@ func RunApi() { VerificationTokenRepo: verificationTokenRepo, SignInAttemptCache: cache.NewCache[service.SignInAttempt](redisClient, "sign-in-attempt", logger), }) - happeningService := service.NewHappeningService(happeningRepo, userRepo, registrationRepo, banInfoRepo, groupRepo) + happeningService := service.NewHappeningService(happeningRepo, userRepo, registrationRepo, banInfoRepo, groupRepo, cmsHappeningRepo, emailClient) degreeService := service.NewDegreeService(degreeRepo) siteFeedbackService := service.NewSiteFeedbackService(siteFeedbackRepo) shoppingListService := service.NewShoppingListService(shoppingListItemRepo, usersToShoppingListItemRepo) userService := service.NewUserService(userRepo, profilePictureRepo, groupRepo, degreeRepo) - strikeService := service.NewStrikeService(dotRepo, banInfoRepo, userRepo) - accessRequestService := service.NewAccessRequestService(accessRequestRepo) + strikeService := service.NewStrikeService(dotRepo, banInfoRepo, userRepo, emailClient) + accessRequestService := service.NewAccessRequestService(accessRequestRepo, whitelistRepo, emailClient) whitelistService := service.NewWhitelistService(whitelistRepo) commentService := service.NewCommentService(commentRepo) weatherService := service.NewWeatherService(weatherRepo) diff --git a/apps/uno/bootstrap/cron.go b/apps/uno/bootstrap/cron.go index 833b346c65..d9a3a3570a 100644 --- a/apps/uno/bootstrap/cron.go +++ b/apps/uno/bootstrap/cron.go @@ -101,7 +101,7 @@ func RunCron(job string) { logger.Warn(context.Background(), "file storage not configured, profile picture features disabled") } questionService := service.NewQuestionService(questionRepo) - strikeService := service.NewStrikeService(dotRepo, banInfoRepo, userRepo) + strikeService := service.NewStrikeService(dotRepo, banInfoRepo, userRepo, nil) userService := service.NewUserService(userRepo, profilePictureRepo, nil, nil) // Job to clean up sensitive questions and strikes every 6 months. diff --git a/apps/uno/config/config.go b/apps/uno/config/config.go index ece738f11a..6c42a16aff 100644 --- a/apps/uno/config/config.go +++ b/apps/uno/config/config.go @@ -46,6 +46,9 @@ type Config struct { SanityAPIToken string SanityAPIVersion string + // Email service configuration + EmailBaseURL string + // Storage configuration DatabaseURL string RedisURL string @@ -104,6 +107,8 @@ func Load() *Config { SanityAPIVersion: getEnvOrDefault("SANITY_API_VERSION", "2023-05-03"), SanityAPIToken: os.Getenv("SANITY_API_TOKEN"), + EmailBaseURL: getEnvOrDefault("EMAIL_BASE_URL", "http://localhost:3001"), + DatabaseURL: os.Getenv("DATABASE_URL"), RedisURL: os.Getenv("REDIS_URL"), } diff --git a/apps/uno/cron/jobs/db_maintenance_test.go b/apps/uno/cron/jobs/db_maintenance_test.go index c9075b5359..ce1992c6c1 100644 --- a/apps/uno/cron/jobs/db_maintenance_test.go +++ b/apps/uno/cron/jobs/db_maintenance_test.go @@ -57,7 +57,7 @@ func TestCleanupOldStrikesRun(t *testing.T) { banInfoRepo := mocks.NewBanInfoRepo(t) userRepo := mocks.NewUserRepo(t) - strikeService := service.NewStrikeService(dotRepo, banInfoRepo, userRepo) + strikeService := service.NewStrikeService(dotRepo, banInfoRepo, userRepo, nil) job := NewCleanupOldStrikes(strikeService, &testutil.NoOpLogger{}) err := job.Run(t.Context()) @@ -77,7 +77,7 @@ func TestCleanupOldStrikesRunError(t *testing.T) { banInfoRepo := mocks.NewBanInfoRepo(t) userRepo := mocks.NewUserRepo(t) - strikeService := service.NewStrikeService(dotRepo, banInfoRepo, userRepo) + strikeService := service.NewStrikeService(dotRepo, banInfoRepo, userRepo, nil) job := NewCleanupOldStrikes(strikeService, &testutil.NoOpLogger{}) err := job.Run(t.Context()) diff --git a/apps/uno/docs/docs.go b/apps/uno/docs/docs.go index e6e829644f..5cbdda52b5 100644 --- a/apps/uno/docs/docs.go +++ b/apps/uno/docs/docs.go @@ -175,6 +175,120 @@ const docTemplate = `{ } } }, + "/access-requests/{id}/approve": { + "post": { + "security": [ + { + "AdminAPIKey": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "access-request" + ], + "summary": "Approve access request", + "parameters": [ + { + "type": "string", + "description": "Access request ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/access-requests/{id}/deny": { + "post": { + "security": [ + { + "AdminAPIKey": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "access-request" + ], + "summary": "Deny access request", + "parameters": [ + { + "type": "string", + "description": "Access request ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Denial payload", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/uno_http_dto.DenyAccessRequestRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, "/advent-of-code/leaderboard": { "get": { "produces": [ @@ -5612,6 +5726,14 @@ const docTemplate = `{ } } }, + "uno_http_dto.DenyAccessRequestRequest": { + "type": "object", + "properties": { + "reason": { + "type": "string" + } + } + }, "uno_http_dto.DeregisterRequest": { "type": "object", "properties": { diff --git a/apps/uno/docs/swagger.json b/apps/uno/docs/swagger.json index c8953376a8..b8ffddec12 100644 --- a/apps/uno/docs/swagger.json +++ b/apps/uno/docs/swagger.json @@ -168,6 +168,120 @@ } } }, + "/access-requests/{id}/approve": { + "post": { + "security": [ + { + "AdminAPIKey": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "access-request" + ], + "summary": "Approve access request", + "parameters": [ + { + "type": "string", + "description": "Access request ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/access-requests/{id}/deny": { + "post": { + "security": [ + { + "AdminAPIKey": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "access-request" + ], + "summary": "Deny access request", + "parameters": [ + { + "type": "string", + "description": "Access request ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Denial payload", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/uno_http_dto.DenyAccessRequestRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, "/advent-of-code/leaderboard": { "get": { "produces": [ @@ -5605,6 +5719,14 @@ } } }, + "uno_http_dto.DenyAccessRequestRequest": { + "type": "object", + "properties": { + "reason": { + "type": "string" + } + } + }, "uno_http_dto.DeregisterRequest": { "type": "object", "properties": { diff --git a/apps/uno/docs/swagger.yaml b/apps/uno/docs/swagger.yaml index fbad0aeb59..29e0ce63b6 100644 --- a/apps/uno/docs/swagger.yaml +++ b/apps/uno/docs/swagger.yaml @@ -775,6 +775,11 @@ definitions: name: type: string type: object + uno_http_dto.DenyAccessRequestRequest: + properties: + reason: + type: string + type: object uno_http_dto.DeregisterRequest: properties: reason: @@ -1478,6 +1483,78 @@ paths: summary: Delete access request tags: - access-request + /access-requests/{id}/approve: + post: + parameters: + - description: Access request ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + "401": + description: Unauthorized + schema: + type: string + "404": + description: Not Found + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + security: + - AdminAPIKey: [] + summary: Approve access request + tags: + - access-request + /access-requests/{id}/deny: + post: + consumes: + - application/json + parameters: + - description: Access request ID + in: path + name: id + required: true + type: string + - description: Denial payload + in: body + name: body + required: true + schema: + $ref: '#/definitions/uno_http_dto.DenyAccessRequestRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + "401": + description: Unauthorized + schema: + type: string + "404": + description: Not Found + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + security: + - AdminAPIKey: [] + summary: Deny access request + tags: + - access-request /advent-of-code/leaderboard: get: produces: diff --git a/apps/uno/domain/port/email.go b/apps/uno/domain/port/email.go new file mode 100644 index 0000000000..c20e61eca7 --- /dev/null +++ b/apps/uno/domain/port/email.go @@ -0,0 +1,15 @@ +package port + +import "context" + +type EmailClient interface { + SendRegistrationConfirmation(ctx context.Context, to []string, subject, title string, isBedpres bool) error + SendDeregistrationNotification(ctx context.Context, to []string, subject, name, reason, happeningTitle string) error + SendGotSpotNotification(ctx context.Context, to []string, subject, name, happeningTitle string) error + SendStrikeNotification(ctx context.Context, to []string, subject, name, reason string, amount int, isBanned bool) error + SendAccessGranted(ctx context.Context, to []string, subject string) error + SendAccessDenied(ctx context.Context, to []string, subject, reason string) error + SendAccessRequestNotification(ctx context.Context, to []string, subject, email, reason string) error + SendEmailVerification(ctx context.Context, to []string, subject, verificationURL, firstName string) error + SendMagicLink(ctx context.Context, to []string, subject, magicLinkURL, code, firstName string) error +} diff --git a/apps/uno/domain/port/mocks/EmailClient.go b/apps/uno/domain/port/mocks/EmailClient.go new file mode 100644 index 0000000000..e6ececd05b --- /dev/null +++ b/apps/uno/domain/port/mocks/EmailClient.go @@ -0,0 +1,719 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + + mock "github.com/stretchr/testify/mock" +) + +// NewEmailClient creates a new instance of EmailClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmailClient(t interface { + mock.TestingT + Cleanup(func()) +}) *EmailClient { + mock := &EmailClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// EmailClient is an autogenerated mock type for the EmailClient type +type EmailClient struct { + mock.Mock +} + +type EmailClient_Expecter struct { + mock *mock.Mock +} + +func (_m *EmailClient) EXPECT() *EmailClient_Expecter { + return &EmailClient_Expecter{mock: &_m.Mock} +} + +// SendAccessDenied provides a mock function for the type EmailClient +func (_mock *EmailClient) SendAccessDenied(ctx context.Context, to []string, subject string, reason string) error { + ret := _mock.Called(ctx, to, subject, reason) + + if len(ret) == 0 { + panic("no return value specified for SendAccessDenied") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []string, string, string) error); ok { + r0 = returnFunc(ctx, to, subject, reason) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// EmailClient_SendAccessDenied_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendAccessDenied' +type EmailClient_SendAccessDenied_Call struct { + *mock.Call +} + +// SendAccessDenied is a helper method to define mock.On call +// - ctx context.Context +// - to []string +// - subject string +// - reason string +func (_e *EmailClient_Expecter) SendAccessDenied(ctx interface{}, to interface{}, subject interface{}, reason interface{}) *EmailClient_SendAccessDenied_Call { + return &EmailClient_SendAccessDenied_Call{Call: _e.mock.On("SendAccessDenied", ctx, to, subject, reason)} +} + +func (_c *EmailClient_SendAccessDenied_Call) Run(run func(ctx context.Context, to []string, subject string, reason string)) *EmailClient_SendAccessDenied_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + if args[1] != nil { + arg1 = args[1].([]string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *EmailClient_SendAccessDenied_Call) Return(err error) *EmailClient_SendAccessDenied_Call { + _c.Call.Return(err) + return _c +} + +func (_c *EmailClient_SendAccessDenied_Call) RunAndReturn(run func(ctx context.Context, to []string, subject string, reason string) error) *EmailClient_SendAccessDenied_Call { + _c.Call.Return(run) + return _c +} + +// SendAccessGranted provides a mock function for the type EmailClient +func (_mock *EmailClient) SendAccessGranted(ctx context.Context, to []string, subject string) error { + ret := _mock.Called(ctx, to, subject) + + if len(ret) == 0 { + panic("no return value specified for SendAccessGranted") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []string, string) error); ok { + r0 = returnFunc(ctx, to, subject) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// EmailClient_SendAccessGranted_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendAccessGranted' +type EmailClient_SendAccessGranted_Call struct { + *mock.Call +} + +// SendAccessGranted is a helper method to define mock.On call +// - ctx context.Context +// - to []string +// - subject string +func (_e *EmailClient_Expecter) SendAccessGranted(ctx interface{}, to interface{}, subject interface{}) *EmailClient_SendAccessGranted_Call { + return &EmailClient_SendAccessGranted_Call{Call: _e.mock.On("SendAccessGranted", ctx, to, subject)} +} + +func (_c *EmailClient_SendAccessGranted_Call) Run(run func(ctx context.Context, to []string, subject string)) *EmailClient_SendAccessGranted_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + if args[1] != nil { + arg1 = args[1].([]string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *EmailClient_SendAccessGranted_Call) Return(err error) *EmailClient_SendAccessGranted_Call { + _c.Call.Return(err) + return _c +} + +func (_c *EmailClient_SendAccessGranted_Call) RunAndReturn(run func(ctx context.Context, to []string, subject string) error) *EmailClient_SendAccessGranted_Call { + _c.Call.Return(run) + return _c +} + +// SendAccessRequestNotification provides a mock function for the type EmailClient +func (_mock *EmailClient) SendAccessRequestNotification(ctx context.Context, to []string, subject string, email string, reason string) error { + ret := _mock.Called(ctx, to, subject, email, reason) + + if len(ret) == 0 { + panic("no return value specified for SendAccessRequestNotification") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []string, string, string, string) error); ok { + r0 = returnFunc(ctx, to, subject, email, reason) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// EmailClient_SendAccessRequestNotification_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendAccessRequestNotification' +type EmailClient_SendAccessRequestNotification_Call struct { + *mock.Call +} + +// SendAccessRequestNotification is a helper method to define mock.On call +// - ctx context.Context +// - to []string +// - subject string +// - email string +// - reason string +func (_e *EmailClient_Expecter) SendAccessRequestNotification(ctx interface{}, to interface{}, subject interface{}, email interface{}, reason interface{}) *EmailClient_SendAccessRequestNotification_Call { + return &EmailClient_SendAccessRequestNotification_Call{Call: _e.mock.On("SendAccessRequestNotification", ctx, to, subject, email, reason)} +} + +func (_c *EmailClient_SendAccessRequestNotification_Call) Run(run func(ctx context.Context, to []string, subject string, email string, reason string)) *EmailClient_SendAccessRequestNotification_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + if args[1] != nil { + arg1 = args[1].([]string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) + }) + return _c +} + +func (_c *EmailClient_SendAccessRequestNotification_Call) Return(err error) *EmailClient_SendAccessRequestNotification_Call { + _c.Call.Return(err) + return _c +} + +func (_c *EmailClient_SendAccessRequestNotification_Call) RunAndReturn(run func(ctx context.Context, to []string, subject string, email string, reason string) error) *EmailClient_SendAccessRequestNotification_Call { + _c.Call.Return(run) + return _c +} + +// SendDeregistrationNotification provides a mock function for the type EmailClient +func (_mock *EmailClient) SendDeregistrationNotification(ctx context.Context, to []string, subject string, name string, reason string, happeningTitle string) error { + ret := _mock.Called(ctx, to, subject, name, reason, happeningTitle) + + if len(ret) == 0 { + panic("no return value specified for SendDeregistrationNotification") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []string, string, string, string, string) error); ok { + r0 = returnFunc(ctx, to, subject, name, reason, happeningTitle) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// EmailClient_SendDeregistrationNotification_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendDeregistrationNotification' +type EmailClient_SendDeregistrationNotification_Call struct { + *mock.Call +} + +// SendDeregistrationNotification is a helper method to define mock.On call +// - ctx context.Context +// - to []string +// - subject string +// - name string +// - reason string +// - happeningTitle string +func (_e *EmailClient_Expecter) SendDeregistrationNotification(ctx interface{}, to interface{}, subject interface{}, name interface{}, reason interface{}, happeningTitle interface{}) *EmailClient_SendDeregistrationNotification_Call { + return &EmailClient_SendDeregistrationNotification_Call{Call: _e.mock.On("SendDeregistrationNotification", ctx, to, subject, name, reason, happeningTitle)} +} + +func (_c *EmailClient_SendDeregistrationNotification_Call) Run(run func(ctx context.Context, to []string, subject string, name string, reason string, happeningTitle string)) *EmailClient_SendDeregistrationNotification_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + if args[1] != nil { + arg1 = args[1].([]string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + var arg5 string + if args[5] != nil { + arg5 = args[5].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + ) + }) + return _c +} + +func (_c *EmailClient_SendDeregistrationNotification_Call) Return(err error) *EmailClient_SendDeregistrationNotification_Call { + _c.Call.Return(err) + return _c +} + +func (_c *EmailClient_SendDeregistrationNotification_Call) RunAndReturn(run func(ctx context.Context, to []string, subject string, name string, reason string, happeningTitle string) error) *EmailClient_SendDeregistrationNotification_Call { + _c.Call.Return(run) + return _c +} + +// SendEmailVerification provides a mock function for the type EmailClient +func (_mock *EmailClient) SendEmailVerification(ctx context.Context, to []string, subject string, verificationURL string, firstName string) error { + ret := _mock.Called(ctx, to, subject, verificationURL, firstName) + + if len(ret) == 0 { + panic("no return value specified for SendEmailVerification") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []string, string, string, string) error); ok { + r0 = returnFunc(ctx, to, subject, verificationURL, firstName) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// EmailClient_SendEmailVerification_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendEmailVerification' +type EmailClient_SendEmailVerification_Call struct { + *mock.Call +} + +// SendEmailVerification is a helper method to define mock.On call +// - ctx context.Context +// - to []string +// - subject string +// - verificationURL string +// - firstName string +func (_e *EmailClient_Expecter) SendEmailVerification(ctx interface{}, to interface{}, subject interface{}, verificationURL interface{}, firstName interface{}) *EmailClient_SendEmailVerification_Call { + return &EmailClient_SendEmailVerification_Call{Call: _e.mock.On("SendEmailVerification", ctx, to, subject, verificationURL, firstName)} +} + +func (_c *EmailClient_SendEmailVerification_Call) Run(run func(ctx context.Context, to []string, subject string, verificationURL string, firstName string)) *EmailClient_SendEmailVerification_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + if args[1] != nil { + arg1 = args[1].([]string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) + }) + return _c +} + +func (_c *EmailClient_SendEmailVerification_Call) Return(err error) *EmailClient_SendEmailVerification_Call { + _c.Call.Return(err) + return _c +} + +func (_c *EmailClient_SendEmailVerification_Call) RunAndReturn(run func(ctx context.Context, to []string, subject string, verificationURL string, firstName string) error) *EmailClient_SendEmailVerification_Call { + _c.Call.Return(run) + return _c +} + +// SendGotSpotNotification provides a mock function for the type EmailClient +func (_mock *EmailClient) SendGotSpotNotification(ctx context.Context, to []string, subject string, name string, happeningTitle string) error { + ret := _mock.Called(ctx, to, subject, name, happeningTitle) + + if len(ret) == 0 { + panic("no return value specified for SendGotSpotNotification") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []string, string, string, string) error); ok { + r0 = returnFunc(ctx, to, subject, name, happeningTitle) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// EmailClient_SendGotSpotNotification_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendGotSpotNotification' +type EmailClient_SendGotSpotNotification_Call struct { + *mock.Call +} + +// SendGotSpotNotification is a helper method to define mock.On call +// - ctx context.Context +// - to []string +// - subject string +// - name string +// - happeningTitle string +func (_e *EmailClient_Expecter) SendGotSpotNotification(ctx interface{}, to interface{}, subject interface{}, name interface{}, happeningTitle interface{}) *EmailClient_SendGotSpotNotification_Call { + return &EmailClient_SendGotSpotNotification_Call{Call: _e.mock.On("SendGotSpotNotification", ctx, to, subject, name, happeningTitle)} +} + +func (_c *EmailClient_SendGotSpotNotification_Call) Run(run func(ctx context.Context, to []string, subject string, name string, happeningTitle string)) *EmailClient_SendGotSpotNotification_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + if args[1] != nil { + arg1 = args[1].([]string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) + }) + return _c +} + +func (_c *EmailClient_SendGotSpotNotification_Call) Return(err error) *EmailClient_SendGotSpotNotification_Call { + _c.Call.Return(err) + return _c +} + +func (_c *EmailClient_SendGotSpotNotification_Call) RunAndReturn(run func(ctx context.Context, to []string, subject string, name string, happeningTitle string) error) *EmailClient_SendGotSpotNotification_Call { + _c.Call.Return(run) + return _c +} + +// SendMagicLink provides a mock function for the type EmailClient +func (_mock *EmailClient) SendMagicLink(ctx context.Context, to []string, subject string, magicLinkURL string, code string, firstName string) error { + ret := _mock.Called(ctx, to, subject, magicLinkURL, code, firstName) + + if len(ret) == 0 { + panic("no return value specified for SendMagicLink") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []string, string, string, string, string) error); ok { + r0 = returnFunc(ctx, to, subject, magicLinkURL, code, firstName) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// EmailClient_SendMagicLink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMagicLink' +type EmailClient_SendMagicLink_Call struct { + *mock.Call +} + +// SendMagicLink is a helper method to define mock.On call +// - ctx context.Context +// - to []string +// - subject string +// - magicLinkURL string +// - code string +// - firstName string +func (_e *EmailClient_Expecter) SendMagicLink(ctx interface{}, to interface{}, subject interface{}, magicLinkURL interface{}, code interface{}, firstName interface{}) *EmailClient_SendMagicLink_Call { + return &EmailClient_SendMagicLink_Call{Call: _e.mock.On("SendMagicLink", ctx, to, subject, magicLinkURL, code, firstName)} +} + +func (_c *EmailClient_SendMagicLink_Call) Run(run func(ctx context.Context, to []string, subject string, magicLinkURL string, code string, firstName string)) *EmailClient_SendMagicLink_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + if args[1] != nil { + arg1 = args[1].([]string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + var arg5 string + if args[5] != nil { + arg5 = args[5].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + ) + }) + return _c +} + +func (_c *EmailClient_SendMagicLink_Call) Return(err error) *EmailClient_SendMagicLink_Call { + _c.Call.Return(err) + return _c +} + +func (_c *EmailClient_SendMagicLink_Call) RunAndReturn(run func(ctx context.Context, to []string, subject string, magicLinkURL string, code string, firstName string) error) *EmailClient_SendMagicLink_Call { + _c.Call.Return(run) + return _c +} + +// SendRegistrationConfirmation provides a mock function for the type EmailClient +func (_mock *EmailClient) SendRegistrationConfirmation(ctx context.Context, to []string, subject string, title string, isBedpres bool) error { + ret := _mock.Called(ctx, to, subject, title, isBedpres) + + if len(ret) == 0 { + panic("no return value specified for SendRegistrationConfirmation") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []string, string, string, bool) error); ok { + r0 = returnFunc(ctx, to, subject, title, isBedpres) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// EmailClient_SendRegistrationConfirmation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendRegistrationConfirmation' +type EmailClient_SendRegistrationConfirmation_Call struct { + *mock.Call +} + +// SendRegistrationConfirmation is a helper method to define mock.On call +// - ctx context.Context +// - to []string +// - subject string +// - title string +// - isBedpres bool +func (_e *EmailClient_Expecter) SendRegistrationConfirmation(ctx interface{}, to interface{}, subject interface{}, title interface{}, isBedpres interface{}) *EmailClient_SendRegistrationConfirmation_Call { + return &EmailClient_SendRegistrationConfirmation_Call{Call: _e.mock.On("SendRegistrationConfirmation", ctx, to, subject, title, isBedpres)} +} + +func (_c *EmailClient_SendRegistrationConfirmation_Call) Run(run func(ctx context.Context, to []string, subject string, title string, isBedpres bool)) *EmailClient_SendRegistrationConfirmation_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + if args[1] != nil { + arg1 = args[1].([]string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + var arg4 bool + if args[4] != nil { + arg4 = args[4].(bool) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) + }) + return _c +} + +func (_c *EmailClient_SendRegistrationConfirmation_Call) Return(err error) *EmailClient_SendRegistrationConfirmation_Call { + _c.Call.Return(err) + return _c +} + +func (_c *EmailClient_SendRegistrationConfirmation_Call) RunAndReturn(run func(ctx context.Context, to []string, subject string, title string, isBedpres bool) error) *EmailClient_SendRegistrationConfirmation_Call { + _c.Call.Return(run) + return _c +} + +// SendStrikeNotification provides a mock function for the type EmailClient +func (_mock *EmailClient) SendStrikeNotification(ctx context.Context, to []string, subject string, name string, reason string, amount int, isBanned bool) error { + ret := _mock.Called(ctx, to, subject, name, reason, amount, isBanned) + + if len(ret) == 0 { + panic("no return value specified for SendStrikeNotification") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []string, string, string, string, int, bool) error); ok { + r0 = returnFunc(ctx, to, subject, name, reason, amount, isBanned) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// EmailClient_SendStrikeNotification_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendStrikeNotification' +type EmailClient_SendStrikeNotification_Call struct { + *mock.Call +} + +// SendStrikeNotification is a helper method to define mock.On call +// - ctx context.Context +// - to []string +// - subject string +// - name string +// - reason string +// - amount int +// - isBanned bool +func (_e *EmailClient_Expecter) SendStrikeNotification(ctx interface{}, to interface{}, subject interface{}, name interface{}, reason interface{}, amount interface{}, isBanned interface{}) *EmailClient_SendStrikeNotification_Call { + return &EmailClient_SendStrikeNotification_Call{Call: _e.mock.On("SendStrikeNotification", ctx, to, subject, name, reason, amount, isBanned)} +} + +func (_c *EmailClient_SendStrikeNotification_Call) Run(run func(ctx context.Context, to []string, subject string, name string, reason string, amount int, isBanned bool)) *EmailClient_SendStrikeNotification_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + if args[1] != nil { + arg1 = args[1].([]string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + var arg5 int + if args[5] != nil { + arg5 = args[5].(int) + } + var arg6 bool + if args[6] != nil { + arg6 = args[6].(bool) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + ) + }) + return _c +} + +func (_c *EmailClient_SendStrikeNotification_Call) Return(err error) *EmailClient_SendStrikeNotification_Call { + _c.Call.Return(err) + return _c +} + +func (_c *EmailClient_SendStrikeNotification_Call) RunAndReturn(run func(ctx context.Context, to []string, subject string, name string, reason string, amount int, isBanned bool) error) *EmailClient_SendStrikeNotification_Call { + _c.Call.Return(run) + return _c +} diff --git a/apps/uno/domain/service/access_request.go b/apps/uno/domain/service/access_request.go index 370c21fb2b..1d15b82782 100644 --- a/apps/uno/domain/service/access_request.go +++ b/apps/uno/domain/service/access_request.go @@ -2,17 +2,27 @@ package service import ( "context" + "fmt" + "time" "uno/domain/model" "uno/domain/port" ) type AccessRequestService struct { accessRequestRepo port.AccessRequestRepo + whitelistRepo port.WhitelistRepo + emailClient port.EmailClient } -func NewAccessRequestService(accessRequestRepo port.AccessRequestRepo) *AccessRequestService { +func NewAccessRequestService( + accessRequestRepo port.AccessRequestRepo, + whitelistRepo port.WhitelistRepo, + emailClient port.EmailClient, +) *AccessRequestService { return &AccessRequestService{ accessRequestRepo: accessRequestRepo, + whitelistRepo: whitelistRepo, + emailClient: emailClient, } } @@ -21,7 +31,22 @@ func (ars *AccessRequestService) GetAccessRequests(ctx context.Context) ([]model } func (ars *AccessRequestService) CreateAccessRequest(ctx context.Context, ar model.NewAccessRequest) (model.AccessRequest, error) { - return ars.accessRequestRepo.CreateAccessRequest(ctx, ar) + created, err := ars.accessRequestRepo.CreateAccessRequest(ctx, ar) + if err != nil { + return model.AccessRequest{}, err + } + + if ars.emailClient != nil { + _ = ars.emailClient.SendAccessRequestNotification( + ctx, + []string{"echo@uib.no"}, + "Forespørsel om tilgang til echo.uib.no", + ar.Email, + ar.Reason, + ) + } + + return created, nil } func (ars *AccessRequestService) GetAccessRequestByID(ctx context.Context, id string) (model.AccessRequest, error) { @@ -31,3 +56,63 @@ func (ars *AccessRequestService) GetAccessRequestByID(ctx context.Context, id st func (ars *AccessRequestService) DeleteAccessRequestByID(ctx context.Context, id string) error { return ars.accessRequestRepo.DeleteAccessRequestByID(ctx, id) } + +func (ars *AccessRequestService) ApproveAccessRequest(ctx context.Context, id string) error { + ar, err := ars.accessRequestRepo.GetAccessRequestByID(ctx, id) + if err != nil { + return err + } + + _, err = ars.whitelistRepo.UpsertWhitelist(ctx, model.NewWhitelist{ + Email: ar.Email, + ExpiresAt: accessRequestNextSemesterStart(), + Reason: fmt.Sprintf("Tilgang etter forespørsel: %s", ar.Reason), + }) + if err != nil { + return err + } + + if err = ars.accessRequestRepo.DeleteAccessRequestByID(ctx, id); err != nil { + return err + } + + if ars.emailClient != nil { + _ = ars.emailClient.SendAccessGranted( + ctx, + []string{ar.Email}, + "Tilgang til echo.uib.no", + ) + } + + return nil +} + +func (ars *AccessRequestService) DenyAccessRequest(ctx context.Context, id, reason string) error { + ar, err := ars.accessRequestRepo.GetAccessRequestByID(ctx, id) + if err != nil { + return err + } + + if err = ars.accessRequestRepo.DeleteAccessRequestByID(ctx, id); err != nil { + return err + } + + if ars.emailClient != nil { + _ = ars.emailClient.SendAccessDenied( + ctx, + []string{ar.Email}, + "Tilgang til echo.uib.no avslått", + reason, + ) + } + + return nil +} + +func accessRequestNextSemesterStart() time.Time { + now := time.Now() + if now.Month() >= time.August { + return time.Date(now.Year()+1, time.January, 1, 0, 0, 0, 0, time.Local) + } + return time.Date(now.Year(), time.August, 1, 0, 0, 0, 0, time.Local) +} diff --git a/apps/uno/domain/service/access_request_test.go b/apps/uno/domain/service/access_request_test.go index c1b20d19a6..335606d54a 100644 --- a/apps/uno/domain/service/access_request_test.go +++ b/apps/uno/domain/service/access_request_test.go @@ -12,7 +12,7 @@ import ( func TestAccessRequestService_GetAccessRequests(t *testing.T) { mockRepo := mocks.NewAccessRequestRepo(t) - accessRequestService := service.NewAccessRequestService(mockRepo) + accessRequestService := service.NewAccessRequestService(mockRepo, nil, nil) mockRepo.EXPECT().GetAccessRequests(t.Context()).Return([]model.AccessRequest{}, nil).Once() requests, err := accessRequestService.GetAccessRequests(t.Context()) @@ -22,7 +22,7 @@ func TestAccessRequestService_GetAccessRequests(t *testing.T) { func TestAccessRequestService_GetAccessRequestByID(t *testing.T) { mockRepo := mocks.NewAccessRequestRepo(t) - accessRequestService := service.NewAccessRequestService(mockRepo) + accessRequestService := service.NewAccessRequestService(mockRepo, nil, nil) id := "request-1" request := testutil.NewFakeStruct[model.AccessRequest]() mockRepo.EXPECT().GetAccessRequestByID(t.Context(), id).Return(request, nil).Once() diff --git a/apps/uno/domain/service/happening.go b/apps/uno/domain/service/happening.go index a8bab15c5c..d7778a6ad9 100644 --- a/apps/uno/domain/service/happening.go +++ b/apps/uno/domain/service/happening.go @@ -18,6 +18,8 @@ type HappeningService struct { registrationRepo port.RegistrationRepo banInfoRepo port.BanInfoRepo groupRepo port.GroupRepo + cmsHappeningRepo port.CMSHappeningRepo + emailClient port.EmailClient } func NewHappeningService( @@ -26,6 +28,8 @@ func NewHappeningService( registrationRepo port.RegistrationRepo, banInfoRepo port.BanInfoRepo, groupRepo port.GroupRepo, + cmsHappeningRepo port.CMSHappeningRepo, + emailClient port.EmailClient, ) *HappeningService { return &HappeningService{ happeningRepo: happeningRepo, @@ -33,6 +37,8 @@ func NewHappeningService( registrationRepo: registrationRepo, banInfoRepo: banInfoRepo, groupRepo: groupRepo, + cmsHappeningRepo: cmsHappeningRepo, + emailClient: emailClient, } } @@ -86,13 +92,120 @@ func (hs *HappeningService) UpdateRegistrationStatus( changedAt *time.Time, unregisterReason *string, ) error { - return hs.registrationRepo.UpdateRegistrationStatus(ctx, userID, happeningID, status, prevStatus, changedBy, changedAt, unregisterReason) + if err := hs.registrationRepo.UpdateRegistrationStatus(ctx, userID, happeningID, status, prevStatus, changedBy, changedAt, unregisterReason); err != nil { + return err + } + + if status == model.RegistrationStatusRegistered && hs.emailClient != nil { + hs.sendGotSpotNotification(ctx, userID, happeningID) + } + + return nil +} + +func (hs *HappeningService) sendGotSpotNotification(ctx context.Context, userID, happeningID string) { + user, err := hs.userRepo.GetUserByID(ctx, userID) + if err != nil { + return + } + + happening, err := hs.happeningRepo.GetHappeningById(ctx, happeningID) + if err != nil { + return + } + + sendTo := user.Email + if user.AlternativeEmail != nil { + sendTo = *user.AlternativeEmail + } + + name := "Ola Nordmann" + if user.Name != nil { + name = *user.Name + } + + subject := fmt.Sprintf("Du har fått plass på %s", happening.Title) + _ = hs.emailClient.SendGotSpotNotification(ctx, []string{sendTo}, subject, name, happening.Title) } func (hs *HappeningService) DeleteAnswersByUserAndHappening(ctx context.Context, userID string, happeningID string) error { return hs.registrationRepo.DeleteAnswersByUserAndHappening(ctx, userID, happeningID) } +// ErrRegistrationNotFound is returned when no registration exists for the given user and happening. +var ErrRegistrationNotFound = errors.New("registration not found") + +// Deregister removes a user from a happening and notifies the happening contacts. +func (hs *HappeningService) Deregister(ctx context.Context, userID, happeningID, reason string) error { + reg, err := hs.registrationRepo.GetByUserAndHappening(ctx, userID, happeningID) + if err != nil { + return err + } + if reg == nil { + return ErrRegistrationNotFound + } + + now := time.Now() + prevStatus := string(reg.Status) + if err = hs.registrationRepo.UpdateRegistrationStatus( + ctx, + userID, + happeningID, + model.RegistrationStatusUnregistered, + &prevStatus, + nil, + &now, + &reason, + ); err != nil { + return err + } + + if err = hs.registrationRepo.DeleteAnswersByUserAndHappening(ctx, userID, happeningID); err != nil { + return err + } + + if hs.emailClient != nil && hs.cmsHappeningRepo != nil { + hs.sendDeregistrationNotification(ctx, userID, happeningID, reason) + } + + return nil +} + +func (hs *HappeningService) sendDeregistrationNotification(ctx context.Context, userID, happeningID, reason string) { + happening, err := hs.happeningRepo.GetHappeningById(ctx, happeningID) + if err != nil { + return + } + + contacts, err := hs.cmsHappeningRepo.GetHappeningContactsBySlug(ctx, happening.Slug) + if err != nil || len(contacts) == 0 { + return + } + + user, err := hs.userRepo.GetUserByID(ctx, userID) + if err != nil { + return + } + + name := "Ukjent" + if user.Name != nil { + name = *user.Name + } + + to := make([]string, 0, len(contacts)) + for _, c := range contacts { + if c.Email != "" { + to = append(to, c.Email) + } + } + if len(to) == 0 { + return + } + + subject := fmt.Sprintf("%s har meldt seg av %s", name, happening.Title) + _ = hs.emailClient.SendDeregistrationNotification(ctx, to, subject, name, reason, happening.Title) +} + func (hs *HappeningService) DeleteRegistrationsByHappeningID(ctx context.Context, happeningID string) error { return hs.registrationRepo.DeleteRegistrationsByHappeningID(ctx, happeningID) } diff --git a/apps/uno/domain/service/happening_test.go b/apps/uno/domain/service/happening_test.go index a3de76c6c0..406a888861 100644 --- a/apps/uno/domain/service/happening_test.go +++ b/apps/uno/domain/service/happening_test.go @@ -32,6 +32,8 @@ func TestHappeningService_GetAllHappenings(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, mockGroupRepo, + nil, + nil, ) happenings, err := happeningService.GetAllHappenings(t.Context()) @@ -270,6 +272,8 @@ func TestHappeningService_Register_ErrorCases(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, mockGroupRepo, + nil, + nil, ) questions := []model.QuestionAnswer{} @@ -359,6 +363,8 @@ func TestHappeningService_Register_RegistrationWindow(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, mockGroupRepo, + nil, + nil, ) questions := []model.QuestionAnswer{} @@ -481,6 +487,8 @@ func TestHappeningService_Register_QuestionValidation(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, mockGroupRepo, + nil, + nil, ) questions := []model.QuestionAnswer{} @@ -649,6 +657,8 @@ func TestHappeningService_Register_Success(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, mockGroupRepo, + nil, + nil, ) questions := []model.QuestionAnswer{} @@ -1110,6 +1120,8 @@ func TestHappeningService_SyncHappening_QuestionsField(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, mockGroupRepo, + nil, + nil, ) data := baseData @@ -1216,6 +1228,8 @@ func TestHappeningService_Register_HostCanSkipSpotRangeCheck(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, mockGroupRepo, + nil, + nil, ) questions := []model.QuestionAnswer{} diff --git a/apps/uno/domain/service/strike.go b/apps/uno/domain/service/strike.go index a1703340e8..b328419308 100644 --- a/apps/uno/domain/service/strike.go +++ b/apps/uno/domain/service/strike.go @@ -29,17 +29,20 @@ type StrikeService struct { dotRepo port.DotRepo banInforepo port.BanInfoRepo userRepo port.UserRepo + emailClient port.EmailClient } func NewStrikeService( dotRepo port.DotRepo, banInfoRepo port.BanInfoRepo, userRepo port.UserRepo, + emailClient port.EmailClient, ) *StrikeService { return &StrikeService{ dotRepo: dotRepo, banInforepo: banInfoRepo, userRepo: userRepo, + emailClient: emailClient, } } @@ -95,7 +98,8 @@ func (s *StrikeService) DeleteDotByIDAndUserID(ctx context.Context, id int, user } func (s *StrikeService) AddStrike(ctx context.Context, userID string, opts AddStrikeOptions) (AddStrikeResult, error) { - if _, err := s.userRepo.GetUserByID(ctx, userID); err != nil { + user, err := s.userRepo.GetUserByID(ctx, userID) + if err != nil { return AddStrikeResult{}, ErrUserNotFound } @@ -148,6 +152,7 @@ func (s *StrikeService) AddStrike(ctx context.Context, userID string, opts AddSt } } + s.sendStrikeNotification(ctx, user, opts.Reason, opts.Count, true) return AddStrikeResult{IsBanned: true}, nil } @@ -161,5 +166,21 @@ func (s *StrikeService) AddStrike(ctx context.Context, userID string, opts AddSt return AddStrikeResult{}, err } + s.sendStrikeNotification(ctx, user, opts.Reason, opts.Count, false) return AddStrikeResult{IsBanned: false}, nil } + +func (s *StrikeService) sendStrikeNotification(ctx context.Context, user model.User, reason string, count int, isBanned bool) { + if s.emailClient == nil { + return + } + sendTo := user.Email + if user.AlternativeEmail != nil { + sendTo = *user.AlternativeEmail + } + name := "Ola Nordmann" + if user.Name != nil { + name = *user.Name + } + _ = s.emailClient.SendStrikeNotification(ctx, []string{sendTo}, "VIKTIG: Du har fått prikk", name, reason, count, isBanned) +} diff --git a/apps/uno/domain/service/strike_test.go b/apps/uno/domain/service/strike_test.go index 5ac323bd89..f4c0112ba0 100644 --- a/apps/uno/domain/service/strike_test.go +++ b/apps/uno/domain/service/strike_test.go @@ -13,7 +13,7 @@ import ( func TestStrikeService_GetUserByID(t *testing.T) { mockUserRepo := mocks.NewUserRepo(t) - strikeService := service.NewStrikeService(nil, nil, mockUserRepo) + strikeService := service.NewStrikeService(nil, nil, mockUserRepo, nil) userID := "user-1" mockUser := testutil.NewFakeStruct(func(u *model.User) { u.ID = userID }) @@ -26,7 +26,7 @@ func TestStrikeService_GetUserByID(t *testing.T) { func TestStrikeService_CreateDot(t *testing.T) { mockDotRepo := mocks.NewDotRepo(t) - strikeService := service.NewStrikeService(mockDotRepo, nil, nil) + strikeService := service.NewStrikeService(mockDotRepo, nil, nil, nil) newDot := testutil.NewFakeStruct[model.NewDot]() mockDot := testutil.NewFakeStruct[model.Dot]() @@ -39,7 +39,7 @@ func TestStrikeService_CreateDot(t *testing.T) { func TestStrikeService_GetBanInfoByUserID(t *testing.T) { mockBanInfoRepo := mocks.NewBanInfoRepo(t) - strikeService := service.NewStrikeService(nil, mockBanInfoRepo, nil) + strikeService := service.NewStrikeService(nil, mockBanInfoRepo, nil, nil) userID := "user-1" banInfo := testutil.NewFakeStruct[model.ModBanInfo]() @@ -59,7 +59,7 @@ func TestStrikeService_UnbanUsersWithExpiredStrikes(t *testing.T) { mockBanInfoRepo := mocks.NewBanInfoRepo(t) mockBanInfoRepo.EXPECT().DeleteExpired(mock.Anything).Return(nil).Once() - strikeService := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo) + strikeService := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo, nil) err := strikeService.UnbanUsersWithExpiredStrikes(t.Context()) assert.NoError(t, err, "Expected no error from UnbanUsersWithExpiredStrikes") @@ -75,7 +75,7 @@ func TestStrikeService_UnbanUsersWithExpiredStrikes_BanInfoErr(t *testing.T) { mockBanInfoRepo := mocks.NewBanInfoRepo(t) mockBanInfoRepo.EXPECT().DeleteExpired(mock.Anything).Return(assert.AnError).Once() - strikeService := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo) + strikeService := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo, nil) err := strikeService.UnbanUsersWithExpiredStrikes(t.Context()) assert.Error(t, err, "Expected error from UnbanUsersWithExpiredStrikes due to BanInfoRepo failure") @@ -90,7 +90,7 @@ func TestStrikeService_UnbanUsersWithExpiredStrikes_DotRepoErr(t *testing.T) { mockBanInfoRepo := mocks.NewBanInfoRepo(t) // Does not get called - strikeServiceErr := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo) + strikeServiceErr := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo, nil) err := strikeServiceErr.UnbanUsersWithExpiredStrikes(t.Context()) assert.Error(t, err, "Expected error from UnbanUsersWithExpiredStrikes due to DotRepo failure") @@ -105,7 +105,7 @@ func TestStrikeService_GetUsersWithStrikeDetails(t *testing.T) { } mockUserRepo.EXPECT().GetUsersWithStrikeDetails(mock.Anything).Return(expectedUsers, nil).Once() - strikeService := service.NewStrikeService(nil, nil, mockUserRepo) + strikeService := service.NewStrikeService(nil, nil, mockUserRepo, nil) users, err := strikeService.GetUsersWithStrikeDetails(t.Context()) assert.NoError(t, err, "Expected no error from GetUsersWithStrikeDetails") diff --git a/apps/uno/http/dto/access_request.go b/apps/uno/http/dto/access_request.go index 742246c922..7f2a00d335 100644 --- a/apps/uno/http/dto/access_request.go +++ b/apps/uno/http/dto/access_request.go @@ -37,6 +37,11 @@ func (r *AccessRequestResponse) FromDomain(ar model.AccessRequest) AccessRequest } } +// DenyAccessRequestRequest represents the payload for denying an access request +type DenyAccessRequestRequest struct { + Reason string `json:"reason"` +} + // AccessRequestsFromDomainList converts a slice of domain models to DTOs func AccessRequestsFromDomainList(accessRequests []model.AccessRequest) []AccessRequestResponse { response := make([]AccessRequestResponse, len(accessRequests)) diff --git a/apps/uno/http/routes/api/access_request.go b/apps/uno/http/routes/api/access_request.go index 03ae40f8e9..8b0f54f28e 100644 --- a/apps/uno/http/routes/api/access_request.go +++ b/apps/uno/http/routes/api/access_request.go @@ -15,7 +15,11 @@ type accessRequests struct { accessRequestService *service.AccessRequestService } -func NewAccessRequestMux(logger port.Logger, accessRequestService *service.AccessRequestService, admin handler.Middleware) *router.Mux { +func NewAccessRequestMux( + logger port.Logger, + accessRequestService *service.AccessRequestService, + admin handler.Middleware, +) *router.Mux { a := accessRequests{logger, accessRequestService} mux := router.NewMux() @@ -23,6 +27,8 @@ func NewAccessRequestMux(logger port.Logger, accessRequestService *service.Acces mux.POST("/", a.createAccessRequest, admin) mux.GET("/", a.getAccessRequests, admin) mux.DELETE("/{id}", a.deleteAccessRequest, admin) + mux.POST("/{id}/approve", a.approveAccessRequest, admin) + mux.POST("/{id}/deny", a.denyAccessRequest, admin) return mux } @@ -37,13 +43,11 @@ func NewAccessRequestMux(logger port.Logger, accessRequestService *service.Acces // @Security AdminAPIKey // @Router /access-requests [get] func (a *accessRequests) getAccessRequests(ctx *handler.Context) error { - // Get domain models from service accessRequests, err := a.accessRequestService.GetAccessRequests(ctx.Context()) if err != nil { return ctx.InternalServerError() } - // Convert to DTOs response := dto.AccessRequestsFromDomainList(accessRequests) return ctx.JSON(response) } @@ -105,3 +109,64 @@ func (a *accessRequests) deleteAccessRequest(ctx *handler.Context) error { return ctx.Ok() } + +// approveAccessRequest approves an access request, adding the email to the whitelist and sending a confirmation email. +// @Summary Approve access request +// @Tags access-request +// @Produce json +// @Param id path string true "Access request ID" +// @Success 200 {string} string "OK" +// @Failure 401 {string} string "Unauthorized" +// @Failure 404 {string} string "Not Found" +// @Failure 500 {string} string "Internal Server Error" +// @Security AdminAPIKey +// @Router /access-requests/{id}/approve [post] +func (a *accessRequests) approveAccessRequest(ctx *handler.Context) error { + id := ctx.PathValue("id") + if id == "" { + return ctx.BadRequest(errors.New("missing access request id")) + } + + if err := a.accessRequestService.ApproveAccessRequest(ctx.Context(), id); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return ctx.NotFound(errors.New("access request not found")) + } + return ctx.InternalServerError() + } + + return ctx.Ok() +} + +// denyAccessRequest denies an access request, deleting it and sending a rejection email. +// @Summary Deny access request +// @Tags access-request +// @Accept json +// @Produce json +// @Param id path string true "Access request ID" +// @Param body body dto.DenyAccessRequestRequest true "Denial payload" +// @Success 200 {string} string "OK" +// @Failure 401 {string} string "Unauthorized" +// @Failure 404 {string} string "Not Found" +// @Failure 500 {string} string "Internal Server Error" +// @Security AdminAPIKey +// @Router /access-requests/{id}/deny [post] +func (a *accessRequests) denyAccessRequest(ctx *handler.Context) error { + id := ctx.PathValue("id") + if id == "" { + return ctx.BadRequest(errors.New("missing access request id")) + } + + var req dto.DenyAccessRequestRequest + if err := ctx.ReadJSON(&req); err != nil { + return ctx.BadRequest(ErrFailedToReadJSON) + } + + if err := a.accessRequestService.DenyAccessRequest(ctx.Context(), id, req.Reason); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return ctx.NotFound(errors.New("access request not found")) + } + return ctx.InternalServerError() + } + + return ctx.Ok() +} diff --git a/apps/uno/http/routes/api/access_request_test.go b/apps/uno/http/routes/api/access_request_test.go index adda50f8b5..0c013e59d0 100644 --- a/apps/uno/http/routes/api/access_request_test.go +++ b/apps/uno/http/routes/api/access_request_test.go @@ -54,7 +54,7 @@ func TestGetAccessRequestsHandler(t *testing.T) { mockAccessRequestRepo := mocks.NewAccessRequestRepo(t) tt.setupMocks(mockAccessRequestRepo) - accessRequestService := service.NewAccessRequestService(mockAccessRequestRepo) + accessRequestService := service.NewAccessRequestService(mockAccessRequestRepo, nil, nil) mux := api.NewAccessRequestMux(testutil.NewTestLogger(), accessRequestService, handler.NoMiddleware) r := httptest.NewRequest(http.MethodGet, "/", nil) @@ -121,7 +121,7 @@ func TestCreateAccessRequestHandler(t *testing.T) { mockAccessRequestRepo := mocks.NewAccessRequestRepo(t) tt.setupMocks(mockAccessRequestRepo) - accessRequestService := service.NewAccessRequestService(mockAccessRequestRepo) + accessRequestService := service.NewAccessRequestService(mockAccessRequestRepo, nil, nil) mux := api.NewAccessRequestMux(testutil.NewTestLogger(), accessRequestService, handler.NoMiddleware) var r *http.Request @@ -195,7 +195,7 @@ func TestDeleteAccessRequestHandler(t *testing.T) { mockAccessRequestRepo := mocks.NewAccessRequestRepo(t) tt.setupMocks(mockAccessRequestRepo) - accessRequestService := service.NewAccessRequestService(mockAccessRequestRepo) + accessRequestService := service.NewAccessRequestService(mockAccessRequestRepo, nil, nil) mux := api.NewAccessRequestMux(testutil.NewTestLogger(), accessRequestService, handler.NoMiddleware) r := httptest.NewRequest(http.MethodDelete, "/"+tt.id, nil) diff --git a/apps/uno/http/routes/api/happening_test.go b/apps/uno/http/routes/api/happening_test.go index d8df2436e5..7f036c931d 100644 --- a/apps/uno/http/routes/api/happening_test.go +++ b/apps/uno/http/routes/api/happening_test.go @@ -66,6 +66,8 @@ func TestGetHappeningsHandler(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, nil, + nil, + nil, ) mux := api.NewHappeningMux(testutil.NewTestLogger(), happeningService, handler.NoMiddleware) @@ -129,6 +131,8 @@ func TestGetHappeningById(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, nil, + nil, + nil, ) mux := api.NewHappeningMux(testutil.NewTestLogger(), happeningService, handler.NoMiddleware) @@ -193,6 +197,8 @@ func TestGetHappeningQuestions(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, nil, + nil, + nil, ) mux := api.NewHappeningMux(testutil.NewTestLogger(), happeningService, handler.NoMiddleware) @@ -331,6 +337,8 @@ func TestRegisterForHappening(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, nil, + nil, + nil, ) mux := api.NewHappeningMux(testutil.NewTestLogger(), happeningService, handler.NoMiddleware) @@ -370,6 +378,8 @@ func TestGetHappeningRegistrationsCountMany(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, nil, + nil, + nil, ) mux := api.NewHappeningMux(testutil.NewTestLogger(), happeningService, handler.NoMiddleware) @@ -403,6 +413,8 @@ func TestGetHappeningRegistrations(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, nil, + nil, + nil, ) mux := api.NewHappeningMux(testutil.NewTestLogger(), happeningService, handler.NoMiddleware) @@ -439,6 +451,8 @@ func TestGetHappeningSpotRanges(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, nil, + nil, + nil, ) mux := api.NewHappeningMux(testutil.NewTestLogger(), happeningService, handler.NoMiddleware) @@ -512,6 +526,8 @@ func TestDeregisterFromHappeningHandler(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, nil, + nil, + nil, ) mux := api.NewHappeningMux(testutil.NewTestLogger(), happeningService, handler.NoMiddleware) @@ -580,6 +596,8 @@ func TestUpdateRegistrationStatusHandler(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, nil, + nil, + nil, ) mux := api.NewHappeningMux(testutil.NewTestLogger(), happeningService, handler.NoMiddleware) @@ -638,6 +656,8 @@ func TestDeleteAllRegistrationsHandler(t *testing.T) { mockRegistrationRepo, mockBanInfoRepo, nil, + nil, + nil, ) mux := api.NewHappeningMux(testutil.NewTestLogger(), happeningService, handler.NoMiddleware) diff --git a/apps/uno/http/routes/api/users_test.go b/apps/uno/http/routes/api/users_test.go index 5255307d74..a462f5ddab 100644 --- a/apps/uno/http/routes/api/users_test.go +++ b/apps/uno/http/routes/api/users_test.go @@ -69,7 +69,7 @@ func TestGetUsersWithStrikeDetails(t *testing.T) { tt.setupMocks(mockUserRepo) - strikeService := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo) + strikeService := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo, nil) mux := newUsersTestMux(t, strikeService) r := httptest.NewRequest(http.MethodGet, "/with-strikes", nil) @@ -138,7 +138,7 @@ func TestAddStrikeHandler(t *testing.T) { mockUserRepo := mocks.NewUserRepo(t) tt.setupMocks(mockDotRepo, mockBanInfoRepo, mockUserRepo) - strikeService := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo) + strikeService := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo, nil) mux := newUsersTestMux(t, strikeService) body, _ := json.Marshal(tt.requestBody) @@ -186,7 +186,7 @@ func TestRemoveBanHandler(t *testing.T) { mockUserRepo := mocks.NewUserRepo(t) tt.setupMocks(mockBanInfoRepo) - strikeService := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo) + strikeService := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo, nil) mux := newUsersTestMux(t, strikeService) r := httptest.NewRequest(http.MethodDelete, "/"+tt.userID+"/ban", nil) @@ -236,7 +236,7 @@ func TestRemoveStrikeHandler(t *testing.T) { mockUserRepo := mocks.NewUserRepo(t) tt.setupMocks(mockDotRepo) - strikeService := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo) + strikeService := service.NewStrikeService(mockDotRepo, mockBanInfoRepo, mockUserRepo, nil) mux := newUsersTestMux(t, strikeService) r := httptest.NewRequest(http.MethodDelete, "/"+tt.userID+"/strikes/"+tt.strikeID, nil) diff --git a/apps/uno/infrastructure/external/email.go b/apps/uno/infrastructure/external/email.go new file mode 100644 index 0000000000..f52138819a --- /dev/null +++ b/apps/uno/infrastructure/external/email.go @@ -0,0 +1,131 @@ +package external + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "time" + "uno/domain/port" +) + +type EmailClient struct { + baseURL string + httpClient *http.Client +} + +func NewEmailClient(baseURL string) port.EmailClient { + return &EmailClient{ + baseURL: baseURL, + httpClient: &http.Client{Timeout: 10 * time.Second}, + } +} + +func (c *EmailClient) post(ctx context.Context, path string, payload any) error { + body, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("email client marshal: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/send/"+path, bytes.NewReader(body)) + if err != nil { + return fmt.Errorf("email client request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return fmt.Errorf("email client send: %w", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode >= 400 { + return fmt.Errorf("email service returned %d", resp.StatusCode) + } + + return nil +} + +func (c *EmailClient) SendRegistrationConfirmation(ctx context.Context, to []string, subject, title string, isBedpres bool) error { + return c.post(ctx, "registration-confirmation", map[string]any{ + "to": to, + "subject": subject, + "title": title, + "isBedpres": isBedpres, + }) +} + +func (c *EmailClient) SendDeregistrationNotification(ctx context.Context, to []string, subject, name, reason, happeningTitle string) error { + return c.post(ctx, "deregistration-notification", map[string]any{ + "to": to, + "subject": subject, + "name": name, + "reason": reason, + "happeningTitle": happeningTitle, + }) +} + +func (c *EmailClient) SendGotSpotNotification(ctx context.Context, to []string, subject, name, happeningTitle string) error { + return c.post(ctx, "got-spot-notification", map[string]any{ + "to": to, + "subject": subject, + "name": name, + "happeningTitle": happeningTitle, + }) +} + +func (c *EmailClient) SendStrikeNotification(ctx context.Context, to []string, subject, name, reason string, amount int, isBanned bool) error { + return c.post(ctx, "strike-notification", map[string]any{ + "to": to, + "subject": subject, + "name": name, + "reason": reason, + "amount": amount, + "isBanned": isBanned, + }) +} + +func (c *EmailClient) SendAccessGranted(ctx context.Context, to []string, subject string) error { + return c.post(ctx, "access-granted", map[string]any{ + "to": to, + "subject": subject, + }) +} + +func (c *EmailClient) SendAccessDenied(ctx context.Context, to []string, subject, reason string) error { + return c.post(ctx, "access-denied", map[string]any{ + "to": to, + "subject": subject, + "reason": reason, + }) +} + +func (c *EmailClient) SendAccessRequestNotification(ctx context.Context, to []string, subject, email, reason string) error { + return c.post(ctx, "access-request-notification", map[string]any{ + "to": to, + "subject": subject, + "email": email, + "reason": reason, + }) +} + +func (c *EmailClient) SendEmailVerification(ctx context.Context, to []string, subject, verificationURL, firstName string) error { + return c.post(ctx, "email-verification", map[string]any{ + "to": to, + "subject": subject, + "verificationUrl": verificationURL, + "firstName": firstName, + }) +} + +func (c *EmailClient) SendMagicLink(ctx context.Context, to []string, subject, magicLinkURL, code, firstName string) error { + return c.post(ctx, "magic-link", map[string]any{ + "to": to, + "subject": subject, + "magicLinkUrl": magicLinkURL, + "code": code, + "firstName": firstName, + }) +} diff --git a/apps/uno/infrastructure/postgres/sanity_webhook_test.go b/apps/uno/infrastructure/postgres/sanity_webhook_test.go index 5bfe963ead..395e25cba1 100644 --- a/apps/uno/infrastructure/postgres/sanity_webhook_test.go +++ b/apps/uno/infrastructure/postgres/sanity_webhook_test.go @@ -61,7 +61,7 @@ func setupWebhookTest(t *testing.T) (*Database, http.Handler) { logger := testutil.NewTestLogger() happeningRepo := NewHappeningRepo(db, logger) groupRepo := NewGroupRepo(db, logger) - happeningService := service.NewHappeningService(happeningRepo, nil, nil, nil, groupRepo) + happeningService := service.NewHappeningService(happeningRepo, nil, nil, nil, groupRepo, nil, nil) mux := api.NewSanityMux(logger, happeningService, handler.NoMiddleware, nil) return db, mux diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 1a1887731c..611710b0de 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -10,7 +10,7 @@ const config = { reactCompiler: true, output: NEXT_OUTPUT, - transpilePackages: ["@echo-webkom/db", "@echo-webkom/lib", "@echo-webkom/email"], + transpilePackages: ["@echo-webkom/db", "@echo-webkom/lib"], experimental: { serverActions: { diff --git a/apps/web/package.json b/apps/web/package.json index b3ebadd23c..da74a002e8 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -15,7 +15,6 @@ }, "dependencies": { "@echo-webkom/db": "workspace:*", - "@echo-webkom/email": "workspace:*", "@echo-webkom/lib": "workspace:*", "@hookform/resolvers": "5.2.2", "@json2csv/plainjs": "7.0.6", diff --git a/apps/web/src/actions/deregister.ts b/apps/web/src/actions/deregister.ts index 2db98f61a4..78704c3a60 100644 --- a/apps/web/src/actions/deregister.ts +++ b/apps/web/src/actions/deregister.ts @@ -1,7 +1,5 @@ "use server"; -import { DeregistrationNotificationEmail } from "@echo-webkom/email"; -import { emailClient } from "@echo-webkom/email/client"; import { z } from "zod"; import { unoWithAdmin } from "@/api/server"; @@ -22,7 +20,6 @@ export const deregister = async (id: string, payload: z.infer []); - - if (contacts.length > 0) { - await emailClient.sendEmail( - contacts.map((contact) => contact.email), - `${user.name ?? "Ukjent"} har meldt seg av ${happening.title}`, - DeregistrationNotificationEmail({ - happeningTitle: happening.title, - name: user.name ?? "Ukjent", - reason: data.reason, - }), - ); - } - return { success: true, message: "Du er nå avmeldt", diff --git a/apps/web/src/actions/update-registration.ts b/apps/web/src/actions/update-registration.ts index b51455c6f8..0965f48b6e 100644 --- a/apps/web/src/actions/update-registration.ts +++ b/apps/web/src/actions/update-registration.ts @@ -1,8 +1,6 @@ "use server"; import { registrationStatusEnum } from "@echo-webkom/db/schemas"; -import { GotSpotNotificationEmail } from "@echo-webkom/email"; -import { emailClient } from "@echo-webkom/email/client"; import { z } from "zod"; import { unoWithAdmin } from "@/api/server"; @@ -58,26 +56,6 @@ export const updateRegistration = async ( changedBy: user.id, }); - if (data.status === "registered") { - const sendTo = exisitingRegistration.userEmail; - - if (!sendTo) { - return { - success: false, - message: "Kunne ikke finne e-post for brukeren", - }; - } - - await emailClient.sendEmail( - [sendTo], - "Du har fått plass!", - GotSpotNotificationEmail({ - happeningTitle: happening.title, - name: exisitingRegistration.userName ?? exisitingRegistration.userEmail ?? undefined, - }), - ); - } - return { success: true, message: "Påmeldingen er endret", diff --git a/apps/web/src/api/uno/client.ts b/apps/web/src/api/uno/client.ts index 7eeea4f9ba..05ce46000c 100644 --- a/apps/web/src/api/uno/client.ts +++ b/apps/web/src/api/uno/client.ts @@ -947,6 +947,18 @@ class AccessRequestApi { const response = await this.client.request("DELETE", `/access-requests/${id}`); return response.status === 200; } + + async approve(id: string) { + const response = await this.client.request("POST", `/access-requests/${id}/approve`); + return response.status === 200; + } + + async deny(id: string, reason: string) { + const response = await this.client.request("POST", `/access-requests/${id}/deny`, { + body: JSON.stringify({ reason }), + }); + return response.status === 200; + } } export interface Degree { diff --git a/apps/web/src/app/(default)/admin/whitelist/_actions/delete-access-request.ts b/apps/web/src/app/(default)/admin/whitelist/_actions/delete-access-request.ts index e2f2d74d51..c2cdc194da 100644 --- a/apps/web/src/app/(default)/admin/whitelist/_actions/delete-access-request.ts +++ b/apps/web/src/app/(default)/admin/whitelist/_actions/delete-access-request.ts @@ -1,8 +1,5 @@ "use server"; -import { AccessDeniedEmail } from "@echo-webkom/email"; -import { emailClient } from "@echo-webkom/email/client"; - import { unoWithAdmin } from "@/api/server"; import { auth } from "@/auth/session"; import { isMemberOf } from "@/lib/memberships"; @@ -14,23 +11,10 @@ export const deleteAccessRequestAction = async (accessRequestId: string, reason: throw new Error("Du har ikke tilgang til å slette forespørsler"); } - const requests = await unoWithAdmin.accessRequests.all(); - const accessRequest = requests.find((row) => row.id === accessRequestId); - - if (!accessRequest) { - throw new Error("Forespørsel ikke funnet"); - } - - await unoWithAdmin.accessRequests.remove(accessRequestId); - - await emailClient.sendEmail( - [accessRequest.email], - "Tilgang til echo.uib.no avslått", - AccessDeniedEmail({ reason }), - ); + await unoWithAdmin.accessRequests.deny(accessRequestId, reason); return { success: true, - message: "Forespørsel slettet", + message: "Forespørsel avslått", }; }; diff --git a/apps/web/src/app/(default)/admin/whitelist/_actions/grant-access.ts b/apps/web/src/app/(default)/admin/whitelist/_actions/grant-access.ts index 2a3590c8ea..ab59cb5f49 100644 --- a/apps/web/src/app/(default)/admin/whitelist/_actions/grant-access.ts +++ b/apps/web/src/app/(default)/admin/whitelist/_actions/grant-access.ts @@ -1,23 +1,9 @@ "use server"; -import { isPostgresIshError } from "@echo-webkom/db/error"; -import { AccessGrantedEmail } from "@echo-webkom/email"; -import { emailClient } from "@echo-webkom/email/client"; - import { unoWithAdmin } from "@/api/server"; import { auth } from "@/auth/session"; import { isMemberOf } from "@/lib/memberships"; -function getNextSemesterStart() { - const now = new Date(); - const year = now.getFullYear(); - const month = now.getMonth(); - if (month >= 7) { - return new Date(year + 1, 0, 1); // January 1st of next year - } - return new Date(year, 7, 1); // August 1st of current year -} - export const grantAccessAction = async (accessRequestId: string) => { const user = await auth(); @@ -28,49 +14,17 @@ export const grantAccessAction = async (accessRequestId: string) => { }; } - const requests = await unoWithAdmin.accessRequests.all(); - const accessRequest = requests.find((row) => row.id === accessRequestId); + const ok = await unoWithAdmin.accessRequests.approve(accessRequestId); - if (!accessRequest) { + if (!ok) { return { success: false, - message: "Forespørsel ikke funnet", + message: "Kunne ikke godkjenne forespørselen", }; } - const expiresAt = getNextSemesterStart(); - - try { - await unoWithAdmin.whitelist.upsert({ - email: accessRequest.email, - expiresAt, - reason: `Tilgang etter forespørsel: ${accessRequest.reason}`, - }); - - await unoWithAdmin.accessRequests.remove(accessRequestId); - } catch (e) { - if (isPostgresIshError(e)) { - if (e.code === "23505") { - return { - success: false, - message: "E-posten er allerede i whitelist", - }; - } - } - return { - success: false, - message: "Kunne ikke legge til i whitelist", - }; - } - - await emailClient.sendEmail( - [accessRequest.email], - "Tilgang til echo.uib.no", - AccessGrantedEmail(), - ); - return { success: true, - message: "Forespørsel slettet", + message: "Forespørsel godkjent", }; }; diff --git a/apps/web/src/app/(default)/auth/logg-inn/_actions/magic-link.ts b/apps/web/src/app/(default)/auth/logg-inn/_actions/magic-link.ts index e1a2d0a918..6ffe56c4a9 100644 --- a/apps/web/src/app/(default)/auth/logg-inn/_actions/magic-link.ts +++ b/apps/web/src/app/(default)/auth/logg-inn/_actions/magic-link.ts @@ -4,11 +4,10 @@ import crypto from "crypto"; import { verificationTokens } from "@echo-webkom/db/schemas"; import { db } from "@echo-webkom/db/serverless"; -import { MagicLinkEmail } from "@echo-webkom/email"; -import { emailClient } from "@echo-webkom/email/client"; import { eq } from "drizzle-orm"; import { DEV, UNO_BASE_URL } from "@/config"; +import { emailClient } from "@/lib/email-client"; import { checkRateLimit } from "@/lib/rate-limit"; import { isValidEmail } from "@/utils/string"; @@ -106,14 +105,12 @@ export async function sendMagicLink(email: string): Promise { console.log("================================"); } - await emailClient.sendEmail( + await emailClient.sendMagicLink( [targetEmail], "Logg inn på echo", - MagicLinkEmail({ - magicLinkUrl, - code, - firstName: existingUser.name?.split(" ")[0] ?? "der", - }), + magicLinkUrl, + code, + existingUser.name?.split(" ")[0] ?? "der", ); return { diff --git a/apps/web/src/app/(default)/auth/tilgang/[id]/_actions/request-access.ts b/apps/web/src/app/(default)/auth/tilgang/[id]/_actions/request-access.ts index 5dcf4b1830..bffbe04aa1 100644 --- a/apps/web/src/app/(default)/auth/tilgang/[id]/_actions/request-access.ts +++ b/apps/web/src/app/(default)/auth/tilgang/[id]/_actions/request-access.ts @@ -1,8 +1,6 @@ "use server"; import { isPostgresIshError } from "@echo-webkom/db/error"; -import { AccessRequestNotificationEmail } from "@echo-webkom/email"; -import { emailClient } from "@echo-webkom/email/client"; import { z } from "zod"; import { unoWithAdmin } from "@/api/server"; @@ -32,15 +30,6 @@ export const requestAccess = async (data: IRequestAccessForm): Promise) const shouldBeBanned = result.isBanned; - const sendToEmail = strikedUser.alternativeEmail ?? strikedUser.email; - - await emailClient.sendEmail( - [sendToEmail], - "VIKTIG: Du har fått prikk", - , - ); - const message = shouldBeBanned ? `Brukeren er bannet i ${data.banExpiresInMonths} måneder` : `Brukeren har fått ${data.count} prikk(er)`; diff --git a/apps/web/src/lib/email-client.ts b/apps/web/src/lib/email-client.ts new file mode 100644 index 0000000000..aa3ae7fce7 --- /dev/null +++ b/apps/web/src/lib/email-client.ts @@ -0,0 +1,34 @@ +import "server-only"; + +const EMAIL_BASE_URL = process.env.EMAIL_BASE_URL; + +async function post(path: string, body: unknown): Promise { + if (!EMAIL_BASE_URL) return; + + const res = await fetch(`${EMAIL_BASE_URL}/send/${path}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + throw new Error(`Email service error: ${res.status} ${res.statusText}`); + } +} + +export const emailClient = { + sendEmailVerification: ( + to: Array, + subject: string, + verificationUrl: string, + firstName?: string, + ) => post("email-verification", { to, subject, verificationUrl, firstName }), + + sendMagicLink: ( + to: Array, + subject: string, + magicLinkUrl: string, + code: string, + firstName?: string, + ) => post("magic-link", { to, subject, magicLinkUrl, code, firstName }), +}; diff --git a/apps/web/src/lib/email-verification.tsx b/apps/web/src/lib/email-verification.tsx index e4147a93ce..a571e5dadb 100644 --- a/apps/web/src/lib/email-verification.tsx +++ b/apps/web/src/lib/email-verification.tsx @@ -1,11 +1,10 @@ import { verificationTokens } from "@echo-webkom/db/schemas"; import { db } from "@echo-webkom/db/serverless"; -import { EmailVerificationEmail } from "@echo-webkom/email"; -import { emailClient } from "@echo-webkom/email/client"; import { addHours } from "date-fns"; import { nanoid } from "nanoid"; import { BASE_URL, DEV } from "@/config"; +import { emailClient } from "@/lib/email-client"; const TOKEN_EXPIRY_HOURS = 24; @@ -45,9 +44,10 @@ export async function sendVerificationEmail(email: string, firstName?: string): console.log("================================"); } - await emailClient.sendEmail( + await emailClient.sendEmailVerification( [email], "Bekreft e-postadressen din - echo", - EmailVerificationEmail({ verificationUrl, firstName }), + verificationUrl, + firstName, ); } diff --git a/cenv.schema.toml b/cenv.schema.toml index be6d96c3bc..15555f9b18 100644 --- a/cenv.schema.toml +++ b/cenv.schema.toml @@ -186,3 +186,13 @@ required = false [[entries]] key = "AOC_SESSION_COOKIE" required = false + +# Email microservice configuration +# --------------------------------- +# The URL of the email microservice used for sending transactional emails. +# Used by: web, uno +[[entries]] +key = "EMAIL_BASE_URL" +required = false +default = "http://localhost:3001" +kind = "Url" diff --git a/package.json b/package.json index 606fe7d930..447e8cf61a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "build": "turbo run build", - "dev": "./check.sh && turbo run dev --filter=@echo-webkom/web --filter=@echo-webkom/cms --filter=@echo-webkom/uno --filter=@echo-webkom/db", + "dev": "./check.sh && turbo run dev --filter=@echo-webkom/web --filter=@echo-webkom/cms --filter=@echo-webkom/uno --filter=@echo-webkom/db --filter=@echo-webkom/email-service", "dev:all": "./check.sh && turbo run dev", "web:dev": "pnpm run --filter=web dev", "cms:dev": "pnpm run --filter=cms dev", @@ -23,7 +23,6 @@ "db:setup": "pnpm db:remove && pnpm db:up && pnpm sleep2s && pnpm db:migrate", "seed": "pnpm --filter=seeder seed:start", "sleep2s": "node -e \"setTimeout(() => process.exit(0), 2000)\"", - "email:preview": "pnpm --filter=email preview", "lint": "echo 'Use pnpm check or pnpm check:fix instead'", "lint:fix": "echo 'Use pnpm check or pnpm check:fix instead'", "format": "echo 'Use pnpm check or pnpm check:fix instead'", diff --git a/packages/email/.gitignore b/packages/email/.gitignore deleted file mode 100644 index 89f9ac04aa..0000000000 --- a/packages/email/.gitignore +++ /dev/null @@ -1 +0,0 @@ -out/ diff --git a/packages/email/client.ts b/packages/email/client.ts deleted file mode 100644 index 48a8d5fbcb..0000000000 --- a/packages/email/client.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable no-console */ -import "server-only"; -import { Resend } from "resend"; - -const API_KEY = process.env.RESEND_API_KEY; -const FROM_EMAIL = "echo "; - -export const emailClient = { - /** - * Only sends an email if the NODE_ENV is production and if the RESEND_API_KEY is set - * else - * It logs the email to the console - * - * @param to the people that should recieve the email - * @param subject the subject of the email - * @param Email the email component to render - */ - sendEmail: async (to: Array, subject: string, component: React.ReactElement) => { - if (process.env.NODE_ENV !== "production" || !API_KEY) { - try { - console.log("\n========== EMAIL SENT (DEV MODE) =========="); - console.log("TO:", to.join(", ")); - console.log("SUBJECT:", subject); - console.log("==========================================\n"); - return { success: true }; - } catch (error) { - console.error("Error rendering email in dev mode:", error); - throw error; - } - } - - const resend = new Resend(API_KEY); - return await resend.emails.send({ - from: FROM_EMAIL, - to, - subject, - react: component, - }); - }, -}; diff --git a/packages/email/index.ts b/packages/email/index.ts deleted file mode 100644 index 68cbd3b281..0000000000 --- a/packages/email/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { render } from "@react-email/render"; - -export { default as RegistrationConfirmationEmail } from "./emails/registration-confirmation"; -export { default as DeregistrationNotificationEmail } from "./emails/deregistration-notification"; -export { default as GotSpotNotificationEmail } from "./emails/got-spot-notification"; -export { default as StrikeNotificationEmail } from "./emails/strike-notification"; -export { default as AccessGrantedEmail } from "./emails/access-granted"; -export { default as AccessDeniedEmail } from "./emails/access-denied"; -export { default as AccessRequestNotificationEmail } from "./emails/access-request-notification"; -export { default as EmailVerificationEmail } from "./emails/email-verification"; -export { default as MagicLinkEmail } from "./emails/magic-link"; diff --git a/packages/email/package.json b/packages/email/package.json deleted file mode 100644 index e458dc2188..0000000000 --- a/packages/email/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "@echo-webkom/email", - "version": "0.0.0", - "private": true, - "main": "./index.ts", - "exports": { - ".": "./index.ts", - "./client": "./client.ts" - }, - "scripts": { - "clean": "rm -rf .turbo node_modules", - "dev": "pnpm with-env email dev --port 9000", - "export": "pnpm with-env email export", - "with-env": "dotenv -e ../../.env --", - "check": "oxlint . && oxfmt --check . && tsc --noEmit", - "check:fix": "oxlint . --fix && oxfmt . && tsc --noEmit" - }, - "dependencies": { - "@react-email/components": "1.0.11", - "@react-email/render": "2.0.5", - "react": "19.2.4", - "react-dom": "19.2.4", - "resend": "6.10.0", - "server-only": "0.0.1" - }, - "devDependencies": { - "@react-email/preview-server": "^5.2.10", - "@types/node": "22.19.7", - "@types/react": "19.2.14", - "dotenv-cli": "11.0.0", - "react-email": "5.2.10", - "typescript": "5.9.3" - } -} diff --git a/packages/email/tsconfig.json b/packages/email/tsconfig.json deleted file mode 100644 index ca9977a35a..0000000000 --- a/packages/email/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "compilerOptions": { - "target": "es2022", - "skipLibCheck": true, - "esModuleInterop": true, - "allowJs": true, - "resolveJsonModule": true, - "isolatedModules": true, - - "strict": true, - "noUncheckedIndexedAccess": true, - "strictNullChecks": true, - "forceConsistentCasingInFileNames": true, - - "composite": true, - - "module": "es2022", - "moduleResolution": "bundler", - "noEmit": true, - - "lib": ["es2022", "dom", "dom.iterable"], - "jsx": "preserve" - }, - "include": ["**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index baeabbf82b..dfa142f2d9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,6 +120,52 @@ importers: specifier: ^4.80.0 version: 4.80.0 + apps/email: + dependencies: + '@hono/node-server': + specifier: ^1.13.7 + version: 1.19.14(hono@4.12.15) + '@react-email/components': + specifier: 1.0.6 + version: 1.0.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-email/render': + specifier: 2.0.4 + version: 2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + hono: + specifier: ^4.7.10 + version: 4.12.15 + react: + specifier: 19.2.4 + version: 19.2.4 + react-dom: + specifier: 19.2.4 + version: 19.2.4(react@19.2.4) + resend: + specifier: 6.9.1 + version: 6.9.1(@react-email/render@2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + devDependencies: + '@types/node': + specifier: 22.19.7 + version: 22.19.7 + '@types/react': + specifier: 19.2.10 + version: 19.2.10 + '@types/react-dom': + specifier: 19.2.3 + version: 19.2.3(@types/react@19.2.10) + dotenv-cli: + specifier: 11.0.0 + version: 11.0.0 + tsdown: + specifier: ^0.21.7 + version: 0.21.10(typescript@5.9.3) + tsx: + specifier: ^4.19.4 + version: 4.21.0 + typescript: + specifier: 5.9.3 + version: 5.9.3 + apps/uno: devDependencies: dotenv-cli: @@ -131,9 +177,6 @@ importers: '@echo-webkom/db': specifier: workspace:* version: link:../../packages/db - '@echo-webkom/email': - specifier: workspace:* - version: link:../../packages/email '@echo-webkom/lib': specifier: workspace:* version: link:../../packages/lib @@ -254,7 +297,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 6.0.1 - version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) dotenv-cli: specifier: 11.0.0 version: 11.0.0 @@ -278,13 +321,13 @@ importers: version: 5.9.3 vite: specifier: 8.0.3 - version: 8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) vite-tsconfig-paths: specifier: 6.1.1 - version: 6.1.1(typescript@5.9.3)(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.1.1(typescript@5.9.3)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: 4.1.2 - version: 4.1.2(@types/node@22.19.7)(jsdom@29.0.1(@noble/hashes@2.0.1))(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@types/node@22.19.7)(jsdom@29.0.1(@noble/hashes@2.0.1))(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) packages/db: dependencies: @@ -383,7 +426,7 @@ importers: version: 5.9.3 vitest: specifier: 4.1.2 - version: 4.1.2(@types/node@22.19.7)(jsdom@29.0.1(@noble/hashes@2.0.1))(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@types/node@22.19.7)(jsdom@29.0.1(@noble/hashes@2.0.1))(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) packages/seeder: dependencies: @@ -622,6 +665,10 @@ packages: resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} + '@babel/generator@8.0.0-rc.3': + resolution: {integrity: sha512-em37/13/nR320G4jab/nIIHZgc2Wz2y/D39lxnTyxB4/D/omPQncl/lSdlnJY1OhQcRGugTSIF2l/69o31C9dA==} + engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -701,10 +748,18 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@8.0.0-rc.3': + resolution: {integrity: sha512-AmwWFx1m8G/a5cXkxLxTiWl+YEoWuoFLUCwqMlNuWO1tqAYITQAbCRPUkyBHv1VOFgfjVOqEj6L3u15J5ZCzTA==} + engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@8.0.0-rc.3': + resolution: {integrity: sha512-8AWCJ2VJJyDFlGBep5GpaaQ9AAaE/FjAcrqI7jyssYhtL7WGV0DOKpJsQqM037xDbpRLHXsY8TwU7zDma7coOw==} + engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -727,6 +782,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@8.0.0-rc.3': + resolution: {integrity: sha512-B20dvP3MfNc/XS5KKCHy/oyWl5IA6Cn9YjXRdDlCjNmUFrjvLXMNUfQq/QUy9fnG2gYkKKcrto2YaF9B32ToOQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} engines: {node: '>=6.9.0'} @@ -1194,6 +1254,10 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@babel/types@8.0.0-rc.3': + resolution: {integrity: sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q==} + engines: {node: ^20.19.0 || >=22.12.0} + '@braintree/sanitize-url@7.1.2': resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} @@ -1438,8 +1502,11 @@ packages: '@emmetio/stream-reader@2.2.0': resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} - '@emnapi/core@1.9.2': - resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} '@emnapi/runtime@1.7.1': resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} @@ -1505,12 +1572,6 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.27.2': - resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.27.3': resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} engines: {node: '>=18'} @@ -1529,12 +1590,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.27.2': - resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.27.3': resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} engines: {node: '>=18'} @@ -1553,12 +1608,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.27.2': - resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.27.3': resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} engines: {node: '>=18'} @@ -1577,12 +1626,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.27.2': - resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.27.3': resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} engines: {node: '>=18'} @@ -1601,12 +1644,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.27.2': - resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.27.3': resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} engines: {node: '>=18'} @@ -1625,12 +1662,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.27.2': - resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.27.3': resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} engines: {node: '>=18'} @@ -1649,12 +1680,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.27.2': - resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.27.3': resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} engines: {node: '>=18'} @@ -1673,12 +1698,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.2': - resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.27.3': resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} engines: {node: '>=18'} @@ -1697,12 +1716,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.27.2': - resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.27.3': resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} engines: {node: '>=18'} @@ -1721,12 +1734,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.27.2': - resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.27.3': resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} engines: {node: '>=18'} @@ -1745,12 +1752,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.27.2': - resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.27.3': resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} engines: {node: '>=18'} @@ -1769,12 +1770,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.27.2': - resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.27.3': resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} engines: {node: '>=18'} @@ -1793,12 +1788,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.27.2': - resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.27.3': resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} engines: {node: '>=18'} @@ -1817,12 +1806,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.27.2': - resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.27.3': resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} engines: {node: '>=18'} @@ -1841,12 +1824,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.27.2': - resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.27.3': resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} engines: {node: '>=18'} @@ -1865,12 +1842,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.27.2': - resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.27.3': resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} engines: {node: '>=18'} @@ -1889,12 +1860,6 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.27.2': - resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.27.3': resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} engines: {node: '>=18'} @@ -1907,12 +1872,6 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.27.2': - resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.27.3': resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} engines: {node: '>=18'} @@ -1931,12 +1890,6 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.2': - resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.27.3': resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} engines: {node: '>=18'} @@ -1949,12 +1902,6 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.27.2': - resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.27.3': resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} engines: {node: '>=18'} @@ -1973,12 +1920,6 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.2': - resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.27.3': resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} engines: {node: '>=18'} @@ -1991,12 +1932,6 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/openharmony-arm64@0.27.2': - resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.27.3': resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} engines: {node: '>=18'} @@ -2015,12 +1950,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.27.2': - resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.27.3': resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} engines: {node: '>=18'} @@ -2039,12 +1968,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.27.2': - resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.27.3': resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} engines: {node: '>=18'} @@ -2063,12 +1986,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.27.2': - resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.27.3': resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} engines: {node: '>=18'} @@ -2087,12 +2004,6 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.27.2': - resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.27.3': resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} engines: {node: '>=18'} @@ -2148,6 +2059,12 @@ packages: '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@hookform/resolvers@3.10.0': resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} peerDependencies: @@ -2693,6 +2610,12 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@next/env@16.1.7': resolution: {integrity: sha512-rJJbIdJB/RQr2F1nylZr/PJzamvNNhfr3brdKP6s/GW850jbtR70QlSfFselvIBbcPUOlQwBakexjFzqLzF6pg==} @@ -2882,6 +2805,9 @@ packages: '@oxc-project/types@0.122.0': resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxfmt/binding-android-arm-eabi@0.43.0': resolution: {integrity: sha512-CgU2s+/9hHZgo0IxVxrbMPrMj+tJ6VM3mD7Mr/4oiz4FNTISLoCvRmB5nk4wAAle045RtRjd86m673jwPyb1OQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3275,6 +3201,9 @@ packages: resolution: {integrity: sha512-djfIGU9n6DRrunlvj2nIDAp17URo/nA4jSXGvf+Gupx8NLLy9fmJBZ3GL8yhqn9lSVc+cKCharjOa3aOBnWbRw==} engines: {node: '>=20.19 <22 || >=22.12'} + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -3965,6 +3894,13 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@react-email/body@0.2.1': + resolution: {integrity: sha512-ljDiQiJDu/Fq//vSIIP0z5Nuvt4+DX1RqGasstChDGJB/14ogd4VdNS9aacoede/ZjGy3o3Qb+cxyS+XgM6SwQ==} + engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + '@react-email/body@0.3.0': resolution: {integrity: sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==} engines: {node: '>=20.0.0'} @@ -4001,6 +3937,13 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc + '@react-email/components@1.0.6': + resolution: {integrity: sha512-3GwOeq+5yyiAcwSf7TnHi/HWKn22lXbwxQmkkAviSwZLlhsRVxvmWqRxvUVfQk/HclDUG+62+sGz9qjfb2Uxjw==} + engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + '@react-email/container@0.0.16': resolution: {integrity: sha512-QWBB56RkkU0AJ9h+qy33gfT5iuZknPC7Un/IjZv9B0QmMIK+WWacc0cH6y2SV5Cv/b99hU94fjEMOOO4enpkbQ==} engines: {node: '>=20.0.0'} @@ -4034,6 +3977,7 @@ packages: '@react-email/html@0.0.12': resolution: {integrity: sha512-KTShZesan+UsreU7PDUV90afrZwU5TLwYlALuCSU0OT+/U8lULNNbAUekg+tGwCnOfIKYtpDPKkAMRdYlqUznw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -4064,6 +4008,13 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc + '@react-email/render@2.0.4': + resolution: {integrity: sha512-kht2oTFQ1SwrLpd882ahTvUtNa9s53CERHstiTbzhm6aR2Hbykp/mQ4tpPvsBGkKAEvKRlDEoooh60Uk6nHK1g==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^18.0 || ^19.0 || ^19.0.0-rc + '@react-email/render@2.0.5': resolution: {integrity: sha512-oAsSpY/vYt9ReDcRQDBLxENwCNAklkE6bvP5Kl9ZlmVr/RZpfhloJp8xc/OZki/YF2nisRRX50aEy8P9v3R5GA==} engines: {node: '>=20.0.0'} @@ -4083,6 +4034,45 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc + '@react-email/tailwind@2.0.3': + resolution: {integrity: sha512-URXb/T2WS4RlNGM5QwekYnivuiVUcU87H0y5sqLl6/Oi3bMmgL0Bmw/W9GeJylC+876Vw+E6NkE0uRiUFIQwGg==} + engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + peerDependencies: + '@react-email/body': 0.2.1 + '@react-email/button': 0.2.1 + '@react-email/code-block': 0.2.1 + '@react-email/code-inline': 0.0.6 + '@react-email/container': 0.0.16 + '@react-email/heading': 0.0.16 + '@react-email/hr': 0.0.12 + '@react-email/img': 0.0.12 + '@react-email/link': 0.0.13 + '@react-email/preview': 0.0.14 + '@react-email/text': 0.1.6 + react: ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@react-email/body': + optional: true + '@react-email/button': + optional: true + '@react-email/code-block': + optional: true + '@react-email/code-inline': + optional: true + '@react-email/container': + optional: true + '@react-email/heading': + optional: true + '@react-email/hr': + optional: true + '@react-email/img': + optional: true + '@react-email/link': + optional: true + '@react-email/preview': + optional: true + '@react-email/tailwind@2.0.7': resolution: {integrity: sha512-kGw80weVFXikcnCXbigTGXGWQ0MRCSYNCudcdkWxebkWYd0FG6/NPoN3V1p/u68/4+NxZwYPVi2fhnp0x23HdA==} engines: {node: '>=20.0.0'} @@ -4159,30 +4149,60 @@ packages: cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.12': resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4190,6 +4210,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4197,6 +4224,13 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4204,6 +4238,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4211,6 +4252,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4218,6 +4266,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4225,32 +4280,65 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==} engines: {node: '>=14.0.0'} cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0-rc.12': resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} + '@rolldown/pluginutils@1.0.0-rc.17': + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} + '@rolldown/pluginutils@1.0.0-rc.3': resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} @@ -5072,6 +5160,9 @@ packages: '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@types/linkify-it@5.0.0': resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} @@ -5127,6 +5218,9 @@ packages: peerDependencies: '@types/react': '*' + '@types/react@19.2.10': + resolution: {integrity: sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==} + '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} @@ -5283,6 +5377,9 @@ packages: xstate: optional: true + '@zone-eu/mailsplit@5.4.8': + resolution: {integrity: sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -5358,6 +5455,10 @@ packages: resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} engines: {node: '>=14'} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -5397,6 +5498,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-kit@3.0.0-beta.1: + resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==} + engines: {node: '>=20.19.0'} + astring@1.9.0: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true @@ -5547,6 +5652,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + birpc@4.0.0: + resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} @@ -5592,6 +5700,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -6150,6 +6262,9 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + delaunator@5.1.0: resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} @@ -6349,6 +6464,15 @@ packages: resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} engines: {node: '>=4'} + dts-resolver@2.1.3: + resolution: {integrity: sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==} + engines: {node: '>=20.19.0'} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -6383,6 +6507,10 @@ packages: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} + encoding-japanese@2.2.0: + resolution: {integrity: sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==} + engines: {node: '>=8.10.0'} + end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} @@ -6451,11 +6579,6 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.27.2: - resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.27.3: resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} engines: {node: '>=18'} @@ -6754,9 +6877,6 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} - get-tsconfig@4.13.0: - resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} - get-tsconfig@4.13.7: resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} @@ -6914,6 +7034,13 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hono@4.12.15: + resolution: {integrity: sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==} + engines: {node: '>=16.9.0'} + + hookable@6.1.1: + resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + hosted-git-info@9.0.2: resolution: {integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -6984,6 +7111,10 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} @@ -7005,6 +7136,10 @@ packages: import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + import-without-cache@0.3.3: + resolution: {integrity: sha512-bDxwDdF04gm550DfZHgffvlX+9kUlcz32UD0AeBTmVPFiWkrexF2XVmiuFFbDhiFuP8fQkrkvI2KdSNPYWAXkQ==} + engines: {node: '>=20.19.0'} + indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -7334,6 +7469,15 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + libbase64@1.3.0: + resolution: {integrity: sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==} + + libmime@5.3.7: + resolution: {integrity: sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==} + + libqp@2.1.1: + resolution: {integrity: sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -7487,6 +7631,9 @@ packages: magicast@0.5.2: resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + mailparser@3.9.1: + resolution: {integrity: sha512-6vHZcco3fWsDMkf4Vz9iAfxvwrKNGbHx0dV1RKVphQ/zaNY34Buc7D37LSa09jeSeybWzYcTPjhiZFxzVRJedA==} + make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -7922,6 +8069,10 @@ packages: node-releases@2.0.37: resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + nodemailer@7.0.11: + resolution: {integrity: sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==} + engines: {node: '>=6.0.0'} + normalize-package-data@8.0.0: resolution: {integrity: sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==} engines: {node: ^20.17.0 || >=22.9.0} @@ -8374,6 +8525,9 @@ packages: pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -8778,6 +8932,15 @@ packages: '@react-email/render': optional: true + resend@6.9.1: + resolution: {integrity: sha512-jFY3qPP2cith1npRXvS7PVdnhbR1CcuzHg65ty5Elv55GKiXhe+nItXuzzoOlKeYJez1iJAo2+8f6ae8sCj0iA==} + engines: {node: '>=20'} + peerDependencies: + '@react-email/render': '*' + peerDependenciesMeta: + '@react-email/render': + optional: true + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -8813,11 +8976,35 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rolldown-plugin-dts@0.23.2: + resolution: {integrity: sha512-PbSqLawLgZBGcOGT3yqWBGn4cX+wh2nt5FuBGdcMHyOhoukmjbhYAl8NT9sE4U38Cm9tqLOIQeOrvzeayM0DLQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@ts-macro/tsc': ^0.3.6 + '@typescript/native-preview': '>=7.0.0-dev.20260325.1' + rolldown: ^1.0.0-rc.12 + typescript: ^5.0.0 || ^6.0.0 + vue-tsc: ~3.2.0 + peerDependenciesMeta: + '@ts-macro/tsc': + optional: true + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + rolldown@1.0.0-rc.12: resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rolldown@1.0.0-rc.17: + resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup@4.57.1: resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -9204,6 +9391,9 @@ packages: engines: {node: '>=16'} hasBin: true + svix@1.84.1: + resolution: {integrity: sha512-K8DPPSZaW/XqXiz1kEyzSHYgmGLnhB43nQCMeKjWGCUpLIpAMMM8kx3rVVOSm6Bo6EHyK1RQLPT4R06skM/MlQ==} + svix@1.88.0: resolution: {integrity: sha512-vm/JrrUd3bVyBE+3L33TIyVSs8gS5fYx7lrISvKlDJXTYX1ACH4REX8P1tHxsSKoZi/rvifM1t0XRc5Vc45THw==} @@ -9274,10 +9464,18 @@ packages: resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} engines: {node: '>=18'} + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + tinypool@2.1.0: resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} engines: {node: ^20.0.0 || >=22.0.0} @@ -9286,6 +9484,10 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tlds@1.261.0: + resolution: {integrity: sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==} + hasBin: true + tldts-core@6.1.86: resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} @@ -9326,6 +9528,10 @@ packages: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -9356,10 +9562,38 @@ packages: peerDependenciesMeta: typescript: optional: true - - tsconfig-paths@4.2.0: - resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} - engines: {node: '>=6'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tsdown@0.21.10: + resolution: {integrity: sha512-3wk73yBhZe/wX7REqSdivNQ84TDs1mJ+IlnzrrEREP70xlJ/AEIzqaI04l/TzMKVIdkTdC3CPaADn2Lk/0SkdA==} + engines: {node: '>=20.19.0'} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.21.10 + '@tsdown/exe': 0.21.10 + '@vitejs/devtools': '*' + publint: ^0.3.0 + typescript: ^5.0.0 || ^6.0.0 + unplugin-unused: ^0.5.0 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -9465,6 +9699,9 @@ packages: ultrahtml@1.6.0: resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} @@ -9561,6 +9798,16 @@ packages: universal-user-agent@7.0.3: resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + unrun@0.2.37: + resolution: {integrity: sha512-AA7vDuYsgeSYVzJMm16UKA+aXFKhy7nFqW9z5l7q44K4ppFWZAMqYS58ePRZbugMLPH0fwwMzD5A8nP0avxwZQ==} + engines: {node: '>=20.19.0'} + hasBin: true + peerDependencies: + synckit: ^0.11.11 + peerDependenciesMeta: + synckit: + optional: true + unstorage@1.17.4: resolution: {integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==} peerDependencies: @@ -9701,6 +9948,7 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuidv7@0.4.4: @@ -10561,6 +10809,15 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@8.0.0-rc.3': + dependencies: + '@babel/parser': 8.0.0-rc.3 + '@babel/types': 8.0.0-rc.3 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': dependencies: '@babel/types': 7.29.0 @@ -10671,8 +10928,12 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@8.0.0-rc.3': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@8.0.0-rc.3': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helper-wrap-function@7.28.6': @@ -10696,6 +10957,10 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/parser@8.0.0-rc.3': + dependencies: + '@babel/types': 8.0.0-rc.3 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -11273,7 +11538,7 @@ snapshots: dependencies: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 - '@babel/parser': 7.27.0 + '@babel/parser': 7.29.2 '@babel/template': 7.28.6 '@babel/types': 7.29.0 debug: 4.4.3(supports-color@8.1.1) @@ -11298,6 +11563,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@8.0.0-rc.3': + dependencies: + '@babel/helper-string-parser': 8.0.0-rc.3 + '@babel/helper-validator-identifier': 8.0.0-rc.3 + '@braintree/sanitize-url@7.1.2': {} '@bramus/specificity@2.4.2': @@ -11553,12 +11823,17 @@ snapshots: '@emmetio/stream-reader@2.2.0': {} - '@emnapi/core@1.9.2': + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 tslib: 2.8.1 optional: true + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 @@ -11650,9 +11925,6 @@ snapshots: '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/aix-ppc64@0.27.2': - optional: true - '@esbuild/aix-ppc64@0.27.3': optional: true @@ -11662,9 +11934,6 @@ snapshots: '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-arm64@0.27.2': - optional: true - '@esbuild/android-arm64@0.27.3': optional: true @@ -11674,9 +11943,6 @@ snapshots: '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/android-arm@0.27.2': - optional: true - '@esbuild/android-arm@0.27.3': optional: true @@ -11686,9 +11952,6 @@ snapshots: '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/android-x64@0.27.2': - optional: true - '@esbuild/android-x64@0.27.3': optional: true @@ -11698,9 +11961,6 @@ snapshots: '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.27.2': - optional: true - '@esbuild/darwin-arm64@0.27.3': optional: true @@ -11710,9 +11970,6 @@ snapshots: '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/darwin-x64@0.27.2': - optional: true - '@esbuild/darwin-x64@0.27.3': optional: true @@ -11722,9 +11979,6 @@ snapshots: '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.27.2': - optional: true - '@esbuild/freebsd-arm64@0.27.3': optional: true @@ -11734,9 +11988,6 @@ snapshots: '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.27.2': - optional: true - '@esbuild/freebsd-x64@0.27.3': optional: true @@ -11746,9 +11997,6 @@ snapshots: '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-arm64@0.27.2': - optional: true - '@esbuild/linux-arm64@0.27.3': optional: true @@ -11758,9 +12006,6 @@ snapshots: '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-arm@0.27.2': - optional: true - '@esbuild/linux-arm@0.27.3': optional: true @@ -11770,9 +12015,6 @@ snapshots: '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-ia32@0.27.2': - optional: true - '@esbuild/linux-ia32@0.27.3': optional: true @@ -11782,9 +12024,6 @@ snapshots: '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-loong64@0.27.2': - optional: true - '@esbuild/linux-loong64@0.27.3': optional: true @@ -11794,9 +12033,6 @@ snapshots: '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-mips64el@0.27.2': - optional: true - '@esbuild/linux-mips64el@0.27.3': optional: true @@ -11806,9 +12042,6 @@ snapshots: '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-ppc64@0.27.2': - optional: true - '@esbuild/linux-ppc64@0.27.3': optional: true @@ -11818,9 +12051,6 @@ snapshots: '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-riscv64@0.27.2': - optional: true - '@esbuild/linux-riscv64@0.27.3': optional: true @@ -11830,9 +12060,6 @@ snapshots: '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-s390x@0.27.2': - optional: true - '@esbuild/linux-s390x@0.27.3': optional: true @@ -11842,18 +12069,12 @@ snapshots: '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/linux-x64@0.27.2': - optional: true - '@esbuild/linux-x64@0.27.3': optional: true '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-arm64@0.27.2': - optional: true - '@esbuild/netbsd-arm64@0.27.3': optional: true @@ -11863,18 +12084,12 @@ snapshots: '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.27.2': - optional: true - '@esbuild/netbsd-x64@0.27.3': optional: true '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-arm64@0.27.2': - optional: true - '@esbuild/openbsd-arm64@0.27.3': optional: true @@ -11884,18 +12099,12 @@ snapshots: '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.27.2': - optional: true - '@esbuild/openbsd-x64@0.27.3': optional: true '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/openharmony-arm64@0.27.2': - optional: true - '@esbuild/openharmony-arm64@0.27.3': optional: true @@ -11905,9 +12114,6 @@ snapshots: '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/sunos-x64@0.27.2': - optional: true - '@esbuild/sunos-x64@0.27.3': optional: true @@ -11917,9 +12123,6 @@ snapshots: '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-arm64@0.27.2': - optional: true - '@esbuild/win32-arm64@0.27.3': optional: true @@ -11929,9 +12132,6 @@ snapshots: '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-ia32@0.27.2': - optional: true - '@esbuild/win32-ia32@0.27.3': optional: true @@ -11941,9 +12141,6 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@esbuild/win32-x64@0.27.2': - optional: true - '@esbuild/win32-x64@0.27.3': optional: true @@ -12006,6 +12203,10 @@ snapshots: '@floating-ui/utils@0.2.11': {} + '@hono/node-server@1.19.14(hono@4.12.15)': + dependencies: + hono: 4.12.15 + '@hookform/resolvers@3.10.0(react-hook-form@7.72.1(react@19.2.4))': dependencies: react-hook-form: 7.72.1(react@19.2.4) @@ -12510,10 +12711,17 @@ snapshots: hls.js: 1.6.15 mux-embed: 5.17.10 - '@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)': + '@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.7.1 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 '@tybys/wasm-util': 0.10.1 optional: true @@ -12675,6 +12883,8 @@ snapshots: '@oxc-project/types@0.122.0': {} + '@oxc-project/types@0.127.0': {} + '@oxfmt/binding-android-arm-eabi@0.43.0': optional: true @@ -12948,6 +13158,10 @@ snapshots: '@portabletext/types@4.0.2': {} + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -13695,6 +13909,10 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@react-email/body@0.2.1(react@19.2.4)': + dependencies: + react: 19.2.4 + '@react-email/body@0.3.0(react@19.2.4)': dependencies: react: 19.2.4 @@ -13742,6 +13960,32 @@ snapshots: transitivePeerDependencies: - react-dom + '@react-email/components@1.0.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@react-email/body': 0.2.1(react@19.2.4) + '@react-email/button': 0.2.1(react@19.2.4) + '@react-email/code-block': 0.2.1(react@19.2.4) + '@react-email/code-inline': 0.0.6(react@19.2.4) + '@react-email/column': 0.0.14(react@19.2.4) + '@react-email/container': 0.0.16(react@19.2.4) + '@react-email/font': 0.0.10(react@19.2.4) + '@react-email/head': 0.0.13(react@19.2.4) + '@react-email/heading': 0.0.16(react@19.2.4) + '@react-email/hr': 0.0.12(react@19.2.4) + '@react-email/html': 0.0.12(react@19.2.4) + '@react-email/img': 0.0.12(react@19.2.4) + '@react-email/link': 0.0.13(react@19.2.4) + '@react-email/markdown': 0.0.18(react@19.2.4) + '@react-email/preview': 0.0.14(react@19.2.4) + '@react-email/render': 2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-email/row': 0.0.13(react@19.2.4) + '@react-email/section': 0.0.17(react@19.2.4) + '@react-email/tailwind': 2.0.3(@react-email/body@0.2.1(react@19.2.4))(@react-email/button@0.2.1(react@19.2.4))(@react-email/code-block@0.2.1(react@19.2.4))(@react-email/code-inline@0.0.6(react@19.2.4))(@react-email/container@0.0.16(react@19.2.4))(@react-email/heading@0.0.16(react@19.2.4))(@react-email/hr@0.0.12(react@19.2.4))(@react-email/img@0.0.12(react@19.2.4))(@react-email/link@0.0.13(react@19.2.4))(@react-email/preview@0.0.14(react@19.2.4))(@react-email/text@0.1.6(react@19.2.4))(react@19.2.4) + '@react-email/text': 0.1.6(react@19.2.4) + react: 19.2.4 + transitivePeerDependencies: + - react-dom + '@react-email/container@0.0.16(react@19.2.4)': dependencies: react: 19.2.4 @@ -13797,6 +14041,13 @@ snapshots: dependencies: react: 19.2.4 + '@react-email/render@2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + html-to-text: 9.0.5 + prettier: 3.8.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + '@react-email/render@2.0.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: html-to-text: 9.0.5 @@ -13812,6 +14063,23 @@ snapshots: dependencies: react: 19.2.4 + '@react-email/tailwind@2.0.3(@react-email/body@0.2.1(react@19.2.4))(@react-email/button@0.2.1(react@19.2.4))(@react-email/code-block@0.2.1(react@19.2.4))(@react-email/code-inline@0.0.6(react@19.2.4))(@react-email/container@0.0.16(react@19.2.4))(@react-email/heading@0.0.16(react@19.2.4))(@react-email/hr@0.0.12(react@19.2.4))(@react-email/img@0.0.12(react@19.2.4))(@react-email/link@0.0.13(react@19.2.4))(@react-email/preview@0.0.14(react@19.2.4))(@react-email/text@0.1.6(react@19.2.4))(react@19.2.4)': + dependencies: + '@react-email/text': 0.1.6(react@19.2.4) + react: 19.2.4 + tailwindcss: 4.2.2 + optionalDependencies: + '@react-email/body': 0.2.1(react@19.2.4) + '@react-email/button': 0.2.1(react@19.2.4) + '@react-email/code-block': 0.2.1(react@19.2.4) + '@react-email/code-inline': 0.0.6(react@19.2.4) + '@react-email/container': 0.0.16(react@19.2.4) + '@react-email/heading': 0.0.16(react@19.2.4) + '@react-email/hr': 0.0.12(react@19.2.4) + '@react-email/img': 0.0.12(react@19.2.4) + '@react-email/link': 0.0.13(react@19.2.4) + '@react-email/preview': 0.0.14(react@19.2.4) + '@react-email/tailwind@2.0.7(@react-email/body@0.3.0(react@19.2.4))(@react-email/button@0.2.1(react@19.2.4))(@react-email/code-block@0.2.1(react@19.2.4))(@react-email/code-inline@0.0.6(react@19.2.4))(@react-email/container@0.0.16(react@19.2.4))(@react-email/heading@0.0.16(react@19.2.4))(@react-email/hr@0.0.12(react@19.2.4))(@react-email/img@0.0.12(react@19.2.4))(@react-email/link@0.0.13(react@19.2.4))(@react-email/preview@0.0.14(react@19.2.4))(@react-email/text@0.1.6(react@19.2.4))(react@19.2.4)': dependencies: '@react-email/text': 0.1.6(react@19.2.4) @@ -13861,55 +14129,106 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-rc.12': optional: true + '@rolldown/binding-android-arm64@1.0.0-rc.17': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.12': optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@napi-rs/wasm-runtime': 1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1) + '@napi-rs/wasm-runtime': 1.1.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + optional: true + '@rolldown/pluginutils@1.0.0-rc.12': {} + '@rolldown/pluginutils@1.0.0-rc.17': {} + '@rolldown/pluginutils@1.0.0-rc.3': {} '@rolldown/pluginutils@1.0.0-rc.7': {} @@ -15030,6 +15349,8 @@ snapshots: '@types/js-yaml@4.0.9': {} + '@types/jsesc@2.5.1': {} + '@types/linkify-it@5.0.0': {} '@types/markdown-it@14.1.2': @@ -15078,6 +15399,10 @@ snapshots: '@types/prismjs@1.26.6': {} + '@types/react-dom@19.2.3(@types/react@19.2.10)': + dependencies: + '@types/react': 19.2.10 + '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: '@types/react': 19.2.14 @@ -15086,6 +15411,10 @@ snapshots: dependencies: '@types/react': 19.2.14 + '@types/react@19.2.10': + dependencies: + csstype: 3.2.3 + '@types/react@19.2.14': dependencies: csstype: 3.2.3 @@ -15179,10 +15508,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitejs/plugin-react@6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) optionalDependencies: babel-plugin-react-compiler: 1.0.0 @@ -15195,13 +15524,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.2(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.2(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.2 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) '@vitest/pretty-format@4.1.2': dependencies: @@ -15287,6 +15616,12 @@ snapshots: transitivePeerDependencies: - '@types/react' + '@zone-eu/mailsplit@5.4.8': + dependencies: + libbase64: 1.3.0 + libmime: 5.3.7 + libqp: 2.1.1 + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -15343,6 +15678,8 @@ snapshots: ansis@3.17.0: {} + ansis@4.2.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -15372,6 +15709,12 @@ snapshots: assertion-error@2.0.1: {} + ast-kit@3.0.0-beta.1: + dependencies: + '@babel/parser': 8.0.0-rc.3 + estree-walker: 3.0.3 + pathe: 2.0.3 + astring@1.9.0: {} astro-expressive-code@0.41.7(astro@6.1.5(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.57.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)): @@ -15589,6 +15932,8 @@ snapshots: binary-extensions@2.3.0: {} + birpc@4.0.0: {} + blake3-wasm@2.1.5: {} boolbase@1.0.0: {} @@ -15644,6 +15989,8 @@ snapshots: cac@6.7.14: {} + cac@7.0.0: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -16213,6 +16560,8 @@ snapshots: defu@6.1.4: {} + defu@6.1.7: {} + delaunator@5.1.0: dependencies: robust-predicates: 3.0.3 @@ -16319,6 +16668,8 @@ snapshots: dset@3.1.4: {} + dts-resolver@2.1.3: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -16359,6 +16710,8 @@ snapshots: empathic@2.0.0: {} + encoding-japanese@2.2.0: {} + end-of-stream@1.4.5: dependencies: once: 1.4.0 @@ -16484,35 +16837,6 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 - esbuild@0.27.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.2 - '@esbuild/android-arm': 0.27.2 - '@esbuild/android-arm64': 0.27.2 - '@esbuild/android-x64': 0.27.2 - '@esbuild/darwin-arm64': 0.27.2 - '@esbuild/darwin-x64': 0.27.2 - '@esbuild/freebsd-arm64': 0.27.2 - '@esbuild/freebsd-x64': 0.27.2 - '@esbuild/linux-arm': 0.27.2 - '@esbuild/linux-arm64': 0.27.2 - '@esbuild/linux-ia32': 0.27.2 - '@esbuild/linux-loong64': 0.27.2 - '@esbuild/linux-mips64el': 0.27.2 - '@esbuild/linux-ppc64': 0.27.2 - '@esbuild/linux-riscv64': 0.27.2 - '@esbuild/linux-s390x': 0.27.2 - '@esbuild/linux-x64': 0.27.2 - '@esbuild/netbsd-arm64': 0.27.2 - '@esbuild/netbsd-x64': 0.27.2 - '@esbuild/openbsd-arm64': 0.27.2 - '@esbuild/openbsd-x64': 0.27.2 - '@esbuild/openharmony-arm64': 0.27.2 - '@esbuild/sunos-x64': 0.27.2 - '@esbuild/win32-arm64': 0.27.2 - '@esbuild/win32-ia32': 0.27.2 - '@esbuild/win32-x64': 0.27.2 - esbuild@0.27.3: optionalDependencies: '@esbuild/aix-ppc64': 0.27.3 @@ -16840,10 +17164,6 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 - get-tsconfig@4.13.0: - dependencies: - resolve-pkg-maps: 1.0.0 - get-tsconfig@4.13.7: dependencies: resolve-pkg-maps: 1.0.0 @@ -17135,6 +17455,10 @@ snapshots: dependencies: react-is: 16.13.1 + hono@4.12.15: {} + + hookable@6.1.1: {} + hosted-git-info@9.0.2: dependencies: lru-cache: 11.2.7 @@ -17212,6 +17536,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.0: + dependencies: + safer-buffer: 2.1.2 + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -17233,6 +17561,8 @@ snapshots: import-meta-resolve@4.2.0: {} + import-without-cache@0.3.3: {} + indent-string@4.0.0: {} index-to-position@1.2.0: {} @@ -17538,6 +17868,17 @@ snapshots: leven@3.1.0: {} + libbase64@1.3.0: {} + + libmime@5.3.7: + dependencies: + encoding-japanese: 2.2.0 + iconv-lite: 0.6.3 + libbase64: 1.3.0 + libqp: 2.1.1 + + libqp@2.1.1: {} + lightningcss-android-arm64@1.32.0: optional: true @@ -17662,6 +18003,19 @@ snapshots: '@babel/types': 7.29.0 source-map-js: 1.2.1 + mailparser@3.9.1: + dependencies: + '@zone-eu/mailsplit': 5.4.8 + encoding-japanese: 2.2.0 + he: 1.2.0 + html-to-text: 9.0.5 + iconv-lite: 0.7.0 + libmime: 5.3.7 + linkify-it: 5.0.0 + nodemailer: 7.0.11 + punycode.js: 2.3.1 + tlds: 1.261.0 + make-dir@2.1.0: dependencies: pify: 4.0.1 @@ -18395,6 +18749,8 @@ snapshots: node-releases@2.0.37: {} + nodemailer@7.0.11: {} + normalize-package-data@8.0.0: dependencies: hosted-git-info: 9.0.2 @@ -18896,6 +19252,8 @@ snapshots: pure-rand@6.1.0: {} + quansync@1.0.0: {} + queue-microtask@1.2.3: {} quick-lru@5.1.1: {} @@ -19466,6 +19824,13 @@ snapshots: optionalDependencies: '@react-email/render': 2.0.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + resend@6.9.1(@react-email/render@2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)): + dependencies: + mailparser: 3.9.1 + svix: 1.84.1 + optionalDependencies: + '@react-email/render': 2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -19510,7 +19875,25 @@ snapshots: robust-predicates@3.0.3: {} - rolldown@1.0.0-rc.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1): + rolldown-plugin-dts@0.23.2(rolldown@1.0.0-rc.17)(typescript@5.9.3): + dependencies: + '@babel/generator': 8.0.0-rc.3 + '@babel/helper-validator-identifier': 8.0.0-rc.3 + '@babel/parser': 8.0.0-rc.3 + '@babel/types': 8.0.0-rc.3 + ast-kit: 3.0.0-beta.1 + birpc: 4.0.0 + dts-resolver: 2.1.3 + get-tsconfig: 4.13.7 + obug: 2.1.1 + picomatch: 4.0.4 + rolldown: 1.0.0-rc.17 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - oxc-resolver + + rolldown@1.0.0-rc.12(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): dependencies: '@oxc-project/types': 0.122.0 '@rolldown/pluginutils': 1.0.0-rc.12 @@ -19527,13 +19910,34 @@ snapshots: '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.12 '@rolldown/binding-linux-x64-musl': 1.0.0-rc.12 '@rolldown/binding-openharmony-arm64': 1.0.0-rc.12 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1) + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.12 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.12 transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' + rolldown@1.0.0-rc.17: + dependencies: + '@oxc-project/types': 0.127.0 + '@rolldown/pluginutils': 1.0.0-rc.17 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-x64': 1.0.0-rc.17 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + rollup@4.57.1: dependencies: '@types/estree': 1.0.8 @@ -20130,6 +20534,11 @@ snapshots: picocolors: 1.1.1 sax: 1.6.0 + svix@1.84.1: + dependencies: + standardwebhooks: 1.0.0 + uuid: 10.0.0 + svix@1.88.0: dependencies: standardwebhooks: 1.0.0 @@ -20216,15 +20625,24 @@ snapshots: tinyexec@1.0.4: {} + tinyexec@1.1.1: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinypool@2.1.0: {} tinyrainbow@3.1.0: {} + tlds@1.261.0: {} + tldts-core@6.1.86: {} tldts-core@7.0.27: {} @@ -20261,6 +20679,8 @@ snapshots: dependencies: punycode: 2.3.1 + tree-kill@1.2.2: {} + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -20288,12 +20708,39 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tsdown@0.21.10(typescript@5.9.3): + dependencies: + ansis: 4.2.0 + cac: 7.0.0 + defu: 6.1.7 + empathic: 2.0.0 + hookable: 6.1.1 + import-without-cache: 0.3.3 + obug: 2.1.1 + picomatch: 4.0.4 + rolldown: 1.0.0-rc.17 + rolldown-plugin-dts: 0.23.2(rolldown@1.0.0-rc.17)(typescript@5.9.3) + semver: 7.7.4 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + unrun: 0.2.37 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@ts-macro/tsc' + - '@typescript/native-preview' + - oxc-resolver + - synckit + - vue-tsc + tslib@2.8.1: {} tsx@4.21.0: dependencies: - esbuild: 0.27.2 - get-tsconfig: 4.13.0 + esbuild: 0.27.3 + get-tsconfig: 4.13.7 optionalDependencies: fsevents: 2.3.3 @@ -20370,6 +20817,11 @@ snapshots: ultrahtml@1.6.0: {} + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + uncrypto@0.1.3: {} undici-types@6.21.0: {} @@ -20480,6 +20932,10 @@ snapshots: universal-user-agent@7.0.3: {} + unrun@0.2.37: + dependencies: + rolldown: 1.0.0-rc.17 + unstorage@1.17.4: dependencies: anymatch: 3.1.3 @@ -20633,12 +21089,12 @@ snapshots: - supports-color - typescript - vite-tsconfig-paths@6.1.1(typescript@5.9.3)(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-tsconfig-paths@6.1.1(typescript@5.9.3)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: debug: 4.4.3(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) - vite: 8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color - typescript @@ -20675,12 +21131,12 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 postcss: 8.5.8 - rolldown: 1.0.0-rc.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1) + rolldown: 1.0.0-rc.12(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) tinyglobby: 0.2.15 optionalDependencies: '@types/node': 22.19.7 @@ -20697,10 +21153,10 @@ snapshots: optionalDependencies: vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.3) - vitest@4.1.2(@types/node@22.19.7)(jsdom@29.0.1(@noble/hashes@2.0.1))(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.2(@types/node@22.19.7)(jsdom@29.0.1(@noble/hashes@2.0.1))(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.2 - '@vitest/mocker': 4.1.2(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.2(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.2 '@vitest/runner': 4.1.2 '@vitest/snapshot': 4.1.2 @@ -20717,7 +21173,7 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.7.1)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@22.19.7)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.19.7 diff --git a/turbo.json b/turbo.json index 4dae14da58..02e6204e58 100644 --- a/turbo.json +++ b/turbo.json @@ -34,10 +34,6 @@ "persistent": true, "cache": false }, - "email:preview": { - "persistent": true, - "cache": false - }, "test:unit": {}, "check": {}, "check:fix": { @@ -72,6 +68,7 @@ "PORT", "SANITY_TOKEN", "RESEND_API_KEY", + "EMAIL_BASE_URL", "ECHOGRAM_API_KEY", "NEXT_PUBLIC_SANITY_PROJECT_ID", "NEXT_PUBLIC_ECHOGRAM_URL",