diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f20f94c --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +node_modules +dist +build +.next +coverage +*.min.js +pnpm-lock.yaml +**/prisma/migrations +**/prisma/client diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..10de7ee --- /dev/null +++ b/.prettierrc @@ -0,0 +1,20 @@ +{ + "semi": false, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 80, + "plugins": ["@trivago/prettier-plugin-sort-imports"], + "importOrder": [ + "^react(-dom)?$", + "^react-", + "^next", + "^@?\\w", + "^@repo/", + "^@/", + "^\\.\\./", + "^\\./" + ], + "importOrderSeparation": false, + "importOrderSortSpecifiers": true +} diff --git a/CLAUDE.md b/CLAUDE.md index 03ee06b..a20abd3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,15 +58,18 @@ The script bumps version in `package.json`, creates a git tag, and pushes it. Gi ### Backend Architecture TRPC routers in `apps/backend/src/router/`: + - Each domain (user, campaign, subscriber, etc.) has its own directory - Pattern: `router.ts` (definition), `mutation.ts` (writes), `query.ts` (reads) Key entry points: + - `apps/backend/src/app.ts` - Express app setup, middleware, routes - `apps/backend/src/trpc.ts` - TRPC context and auth - `apps/backend/src/cron/` - Scheduled jobs (email sending, maintenance) Endpoints: + - `/trpc/*` - TRPC RPC endpoints - `/api/*` - REST API (Swagger documented) - `/t/:id` - Link tracking redirect @@ -75,6 +78,7 @@ Endpoints: ### Frontend Architecture React Router app in `apps/web/src/`: + - `app.tsx` - Route definitions - `pages/` - Page components matching routes - TRPC client with React Query for data fetching diff --git a/Dockerfile b/Dockerfile index dc48c10..6394bfe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ COPY packages/eslint-config/package.json ./packages/eslint-config/ COPY packages/typescript-config/package.json ./packages/typescript-config/ COPY packages/ui/package.json ./packages/ui/ -RUN pnpm install --frozen-lockfile +RUN timeout 60 pnpm install --frozen-lockfile COPY . . diff --git a/Dockerfile.node b/Dockerfile.node index 814fae8..341ac22 100644 --- a/Dockerfile.node +++ b/Dockerfile.node @@ -12,7 +12,7 @@ COPY packages/eslint-config/package.json ./packages/eslint-config/ COPY packages/typescript-config/package.json ./packages/typescript-config/ COPY packages/ui/package.json ./packages/ui/ -RUN pnpm install --frozen-lockfile +RUN timeout 60 pnpm install --frozen-lockfile COPY . . diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d201bb5..fbe7ea9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -9,3 +9,4 @@ - Added ESLint configurations for all packages and apps - Updated TypeScript configurations across the monorepo - Consolidated `@types/react` version overrides for consistency +- Added Prettier configuration with automatic import sorting diff --git a/apps/backend/eslint.config.mjs b/apps/backend/eslint.config.mjs index 842efe3..49e0cf6 100644 --- a/apps/backend/eslint.config.mjs +++ b/apps/backend/eslint.config.mjs @@ -1,5 +1,5 @@ -import globals from "globals" import pluginJs from "@eslint/js" +import globals from "globals" import * as tseslint from "typescript-eslint" export default tseslint.config({ diff --git a/apps/backend/prisma/seed.ts b/apps/backend/prisma/seed.ts index 2e07b8f..057845e 100644 --- a/apps/backend/prisma/seed.ts +++ b/apps/backend/prisma/seed.ts @@ -1,7 +1,7 @@ +import dayjs from "dayjs" import { hashPassword } from "../src/utils/auth" import { prisma } from "../src/utils/prisma" -import { SmtpEncryption, type Prisma } from "./client" -import dayjs from "dayjs" +import { type Prisma, SmtpEncryption } from "./client" async function seed() { if (!(await prisma.organization.findFirst())) { diff --git a/apps/backend/src/api/middleware.ts b/apps/backend/src/api/middleware.ts index 594167f..9e16d77 100644 --- a/apps/backend/src/api/middleware.ts +++ b/apps/backend/src/api/middleware.ts @@ -1,5 +1,5 @@ -import { prisma } from "../utils/prisma" import express, { NextFunction } from "express" +import { prisma } from "../utils/prisma" export const authenticateApiKey = async ( req: express.Request, diff --git a/apps/backend/src/api/server.ts b/apps/backend/src/api/server.ts index b1fd5ce..e523c37 100644 --- a/apps/backend/src/api/server.ts +++ b/apps/backend/src/api/server.ts @@ -1,13 +1,13 @@ +import crypto from "crypto" +import dayjs from "dayjs" import express from "express" -import { prisma } from "../utils/prisma" -import { authenticateApiKey } from "./middleware" +import fs from "fs/promises" +import path from "path" import { z } from "zod" import { Prisma } from "../../prisma/client" -import crypto from "crypto" import { Mailer } from "../lib/Mailer" -import fs from "fs/promises" -import path from "path" -import dayjs from "dayjs" +import { prisma } from "../utils/prisma" +import { authenticateApiKey } from "./middleware" export const apiRouter = express.Router() diff --git a/apps/backend/src/app.ts b/apps/backend/src/app.ts index 862e863..85aaddb 100644 --- a/apps/backend/src/app.ts +++ b/apps/backend/src/app.ts @@ -1,26 +1,25 @@ import * as trpcExpress from "@trpc/server/adapters/express" -import path from "path" -import express from "express" import cors from "cors" -import { prisma } from "./utils/prisma" +import express from "express" +import path from "path" import swaggerUi from "swagger-ui-express" - -import { createContext, router } from "./trpc" -import { userRouter } from "./user/router" -import { listRouter } from "./list/router" -import { organizationRouter } from "./organization/router" -import { subscriberRouter } from "./subscriber/router" -import { templateRouter } from "./template/router" +import { apiRouter } from "./api/server" import { campaignRouter } from "./campaign/router" +import { ONE_PX_PNG } from "./constants" +import { dashboardRouter } from "./dashboard/router" +import { listRouter } from "./list/router" import { messageRouter } from "./message/router" +import { organizationRouter } from "./organization/router" import { settingsRouter } from "./settings/router" -import { webhookRouter } from "./webhook/router" -import swaggerSpec from "./swagger" -import { apiRouter } from "./api/server" -import { dashboardRouter } from "./dashboard/router" import { statsRouter } from "./stats/router" -import { ONE_PX_PNG } from "./constants" +import { subscriberRouter } from "./subscriber/router" +import swaggerSpec from "./swagger" +import { templateRouter } from "./template/router" +import { createContext, router } from "./trpc" +import { userRouter } from "./user/router" +import { prisma } from "./utils/prisma" import { handleWebhook } from "./webhook/handler" +import { webhookRouter } from "./webhook/router" const appRouter = router({ user: userRouter, diff --git a/apps/backend/src/campaign/mutation.ts b/apps/backend/src/campaign/mutation.ts index bf7f124..8bcc7bc 100644 --- a/apps/backend/src/campaign/mutation.ts +++ b/apps/backend/src/campaign/mutation.ts @@ -1,9 +1,9 @@ -import { z } from "zod" -import { authProcedure } from "../trpc" -import { prisma } from "../utils/prisma" import { TRPCError } from "@trpc/server" import pMap from "p-map" +import { z } from "zod" import { Mailer } from "../lib/Mailer" +import { authProcedure } from "../trpc" +import { prisma } from "../utils/prisma" const createCampaignSchema = z.object({ title: z.string().min(1, "Campaign title is required"), diff --git a/apps/backend/src/campaign/query.ts b/apps/backend/src/campaign/query.ts index 7c5eb63..4db9aef 100644 --- a/apps/backend/src/campaign/query.ts +++ b/apps/backend/src/campaign/query.ts @@ -1,11 +1,11 @@ +import { TRPCError } from "@trpc/server" import { z } from "zod" +import { Prisma } from "../../prisma/client" import { authProcedure } from "../trpc" +import { messageStatus } from "../utils/message-status" +import { resolveProps } from "../utils/pProps" import { prisma } from "../utils/prisma" -import { TRPCError } from "@trpc/server" import { paginationSchema } from "../utils/schemas" -import { Prisma } from "../../prisma/client" -import { resolveProps } from "../utils/pProps" -import { messageStatus } from "../utils/message-status" export const listCampaigns = authProcedure .input(z.object({ organizationId: z.string() }).merge(paginationSchema)) diff --git a/apps/backend/src/campaign/router.ts b/apps/backend/src/campaign/router.ts index a1bc66a..786ff65 100644 --- a/apps/backend/src/campaign/router.ts +++ b/apps/backend/src/campaign/router.ts @@ -1,12 +1,12 @@ import { router } from "../trpc" import { + cancelCampaign, createCampaign, - updateCampaign, deleteCampaign, - startCampaign, - cancelCampaign, - sendTestEmail, duplicateCampaign, + sendTestEmail, + startCampaign, + updateCampaign, } from "./mutation" import { getCampaign, listCampaigns } from "./query" diff --git a/apps/backend/src/cron/cleanupWebhookLogs.ts b/apps/backend/src/cron/cleanupWebhookLogs.ts index ec2eae4..d7be73d 100644 --- a/apps/backend/src/cron/cleanupWebhookLogs.ts +++ b/apps/backend/src/cron/cleanupWebhookLogs.ts @@ -1,6 +1,6 @@ -import { cronJob } from "./cron.utils" -import { prisma } from "../utils/prisma" import dayjs from "dayjs" +import { prisma } from "../utils/prisma" +import { cronJob } from "./cron.utils" export const cleanupWebhookLogsCron = cronJob( "cleanup-webhook-logs", diff --git a/apps/backend/src/cron/cron.ts b/apps/backend/src/cron/cron.ts index 059ed0f..771662a 100644 --- a/apps/backend/src/cron/cron.ts +++ b/apps/backend/src/cron/cron.ts @@ -1,8 +1,8 @@ import cron from "node-cron" -import { sendMessagesCron } from "./sendMessages" +import { cleanupWebhookLogsCron } from "./cleanupWebhookLogs" import { dailyMaintenanceCron } from "./dailyMaintenance" import { processQueuedCampaigns } from "./processQueuedCampaigns" -import { cleanupWebhookLogsCron } from "./cleanupWebhookLogs" +import { sendMessagesCron } from "./sendMessages" type CronJob = { name: string diff --git a/apps/backend/src/cron/dailyMaintenance.ts b/apps/backend/src/cron/dailyMaintenance.ts index a9eef0f..deacb5c 100644 --- a/apps/backend/src/cron/dailyMaintenance.ts +++ b/apps/backend/src/cron/dailyMaintenance.ts @@ -1,6 +1,6 @@ -import { cronJob } from "./cron.utils" -import { prisma } from "../utils/prisma" import dayjs from "dayjs" +import { prisma } from "../utils/prisma" +import { cronJob } from "./cron.utils" export const dailyMaintenanceCron = cronJob("daily-maintenance", async () => { const organizations = await prisma.organization.findMany({ diff --git a/apps/backend/src/cron/processQueuedCampaigns.ts b/apps/backend/src/cron/processQueuedCampaigns.ts index 4a941a7..533ba2d 100644 --- a/apps/backend/src/cron/processQueuedCampaigns.ts +++ b/apps/backend/src/cron/processQueuedCampaigns.ts @@ -1,12 +1,12 @@ -import { prisma } from "../utils/prisma" -import { LinkTracker } from "../lib/LinkTracker" +import pMap from "p-map" import { v4 as uuidV4 } from "uuid" +import { Prisma, Subscriber, SubscriberMetadata } from "../../prisma/client" +import { LinkTracker } from "../lib/LinkTracker" import { - replacePlaceholders, PlaceholderDataKey, + replacePlaceholders, } from "../utils/placeholder-parser" -import pMap from "p-map" -import { Subscriber, Prisma, SubscriberMetadata } from "../../prisma/client" +import { prisma } from "../utils/prisma" import { cronJob } from "./cron.utils" // TODO: Make this a config diff --git a/apps/backend/src/cron/sendMessages.ts b/apps/backend/src/cron/sendMessages.ts index 86522b1..ccd4e30 100644 --- a/apps/backend/src/cron/sendMessages.ts +++ b/apps/backend/src/cron/sendMessages.ts @@ -1,11 +1,10 @@ +import { subSeconds } from "date-fns" import pMap from "p-map" import { Mailer } from "../lib/Mailer" import { logger } from "../utils/logger" -import { prisma } from "../utils/prisma" import { messageStatus } from "../utils/message-status" - +import { prisma } from "../utils/prisma" import { cronJob } from "./cron.utils" -import { subSeconds } from "date-fns" export const sendMessagesCron = cronJob("sendMessages", async () => { const organizations = await prisma.organization.findMany() diff --git a/apps/backend/src/dashboard/query.ts b/apps/backend/src/dashboard/query.ts index 273744e..de8508a 100644 --- a/apps/backend/src/dashboard/query.ts +++ b/apps/backend/src/dashboard/query.ts @@ -1,12 +1,12 @@ -import { z } from "zod" -import { authProcedure } from "../trpc" -import { prisma } from "../utils/prisma" import { TRPCError } from "@trpc/server" +import { subMonths } from "date-fns" +import pMap from "p-map" +import { z } from "zod" import { MessageStatus } from "../../prisma/client" import { countDbSize, subscriberGrowthQuery } from "../../prisma/client/sql" -import pMap from "p-map" -import { subMonths } from "date-fns" +import { authProcedure } from "../trpc" import { messageStatus } from "../utils/message-status" +import { prisma } from "../utils/prisma" export const getDashboardStats = authProcedure .input( @@ -144,7 +144,8 @@ export const getDashboardStats = authProcedure continue } - const prev = subscriberGrowthCumulative[i - 1]?.count ?? baselineSubscriberCount + const prev = + subscriberGrowthCumulative[i - 1]?.count ?? baselineSubscriberCount subscriberGrowthCumulative.push({ date: point.date, diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index 08dd583..2a36d34 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -1,11 +1,11 @@ -export type * from "./app" -export type * from "../prisma/client" -export type * from "./types" - import { app } from "./app" import { initializeCronJobs } from "./cron/cron" import { prisma } from "./utils/prisma" +export type * from "./app" +export type * from "../prisma/client" +export type * from "./types" + const cronController = initializeCronJobs() const PORT = process.env.PORT || 5000 diff --git a/apps/backend/src/lib/Mailer.ts b/apps/backend/src/lib/Mailer.ts index 6757a6b..345257f 100644 --- a/apps/backend/src/lib/Mailer.ts +++ b/apps/backend/src/lib/Mailer.ts @@ -1,6 +1,6 @@ +import nodemailer from "nodemailer" import SMTPTransport from "nodemailer/lib/smtp-transport" import { SmtpSettings } from "../../prisma/client" -import nodemailer from "nodemailer" import { stripAngleBrackets } from "../utils/message-id" type SendMailOptions = { diff --git a/apps/backend/src/list/mutation.ts b/apps/backend/src/list/mutation.ts index 2f09c48..4f9ac38 100644 --- a/apps/backend/src/list/mutation.ts +++ b/apps/backend/src/list/mutation.ts @@ -1,7 +1,7 @@ +import { TRPCError } from "@trpc/server" import { z } from "zod" import { authProcedure } from "../trpc" import { prisma } from "../utils/prisma" -import { TRPCError } from "@trpc/server" const createListSchema = z.object({ name: z.string().min(1, "List name is required"), diff --git a/apps/backend/src/list/query.ts b/apps/backend/src/list/query.ts index 062f409..3474798 100644 --- a/apps/backend/src/list/query.ts +++ b/apps/backend/src/list/query.ts @@ -1,9 +1,9 @@ +import { TRPCError } from "@trpc/server" import { z } from "zod" +import { Prisma } from "../../prisma/client" import { authProcedure } from "../trpc" import { prisma } from "../utils/prisma" -import { TRPCError } from "@trpc/server" import { paginationSchema } from "../utils/schemas" -import { Prisma } from "../../prisma/client" export const getLists = authProcedure .input( diff --git a/apps/backend/src/list/router.ts b/apps/backend/src/list/router.ts index 16854fa..4af0880 100644 --- a/apps/backend/src/list/router.ts +++ b/apps/backend/src/list/router.ts @@ -1,5 +1,5 @@ import { router } from "../trpc" -import { createList, updateList, deleteList } from "./mutation" +import { createList, deleteList, updateList } from "./mutation" import { getList, getLists } from "./query" export const listRouter = router({ diff --git a/apps/backend/src/message/mutation.ts b/apps/backend/src/message/mutation.ts index 5d3de19..0150ddb 100644 --- a/apps/backend/src/message/mutation.ts +++ b/apps/backend/src/message/mutation.ts @@ -1,8 +1,8 @@ +import { TRPCError } from "@trpc/server" import { z } from "zod" +import { MessageStatus } from "../../prisma/client" import { authProcedure } from "../trpc" import { prisma } from "../utils/prisma" -import { TRPCError } from "@trpc/server" -import { MessageStatus } from "../../prisma/client" export const resendMessage = authProcedure .input( diff --git a/apps/backend/src/message/query.ts b/apps/backend/src/message/query.ts index 9b4c62b..3ac3b88 100644 --- a/apps/backend/src/message/query.ts +++ b/apps/backend/src/message/query.ts @@ -1,9 +1,9 @@ +import { TRPCError } from "@trpc/server" import { z } from "zod" +import { Prisma } from "../../prisma/client" import { authProcedure } from "../trpc" import { prisma } from "../utils/prisma" -import { TRPCError } from "@trpc/server" import { paginationSchema } from "../utils/schemas" -import { Prisma } from "../../prisma/client" const messageStatusEnum = z.enum([ "QUEUED", diff --git a/apps/backend/src/message/router.ts b/apps/backend/src/message/router.ts index e8e41aa..9710bcf 100644 --- a/apps/backend/src/message/router.ts +++ b/apps/backend/src/message/router.ts @@ -1,6 +1,6 @@ import { router } from "../trpc" -import { listMessages, getMessage } from "./query" import { resendMessage } from "./mutation" +import { getMessage, listMessages } from "./query" export const messageRouter = router({ list: listMessages, diff --git a/apps/backend/src/organization/mutation.ts b/apps/backend/src/organization/mutation.ts index 560a268..0a6b7bd 100644 --- a/apps/backend/src/organization/mutation.ts +++ b/apps/backend/src/organization/mutation.ts @@ -1,8 +1,8 @@ +import { TRPCError } from "@trpc/server" +import fs from "fs/promises" import { z } from "zod" import { authProcedure } from "../trpc" import { prisma } from "../utils/prisma" -import fs from "fs/promises" -import { TRPCError } from "@trpc/server" const createOrganizationSchema = z.object({ name: z.string().min(1, "Organization name is required"), diff --git a/apps/backend/src/organization/query.ts b/apps/backend/src/organization/query.ts index 540240f..35ad8c0 100644 --- a/apps/backend/src/organization/query.ts +++ b/apps/backend/src/organization/query.ts @@ -1,7 +1,7 @@ +import { TRPCError } from "@trpc/server" import { z } from "zod" import { authProcedure } from "../trpc" import { prisma } from "../utils/prisma" -import { TRPCError } from "@trpc/server" export const getOrganizationById = authProcedure .input( diff --git a/apps/backend/src/settings/mutation.ts b/apps/backend/src/settings/mutation.ts index a365851..077800a 100644 --- a/apps/backend/src/settings/mutation.ts +++ b/apps/backend/src/settings/mutation.ts @@ -1,9 +1,9 @@ -import { z } from "zod" -import { authProcedure } from "../trpc" -import { prisma } from "../utils/prisma" import { TRPCError } from "@trpc/server" import { randomBytes } from "crypto" +import { z } from "zod" import { Mailer } from "../lib/Mailer" +import { authProcedure } from "../trpc" +import { prisma } from "../utils/prisma" const smtpSchema = z.object({ organizationId: z.string(), diff --git a/apps/backend/src/settings/query.ts b/apps/backend/src/settings/query.ts index c67925c..2b86f4f 100644 --- a/apps/backend/src/settings/query.ts +++ b/apps/backend/src/settings/query.ts @@ -1,7 +1,7 @@ +import { TRPCError } from "@trpc/server" import { z } from "zod" import { authProcedure } from "../trpc" import { prisma } from "../utils/prisma" -import { TRPCError } from "@trpc/server" export const getSmtp = authProcedure .input( diff --git a/apps/backend/src/settings/router.ts b/apps/backend/src/settings/router.ts index 79ab868..db34950 100644 --- a/apps/backend/src/settings/router.ts +++ b/apps/backend/src/settings/router.ts @@ -1,21 +1,21 @@ import { router } from "../trpc" import { - getSmtp, - getGeneral, - listApiKeys, - listWebhooks, - getEmailDelivery, -} from "./query" -import { - updateSmtp, - testSmtp, - updateGeneral, createApiKey, - deleteApiKey, createWebhook, + deleteApiKey, deleteWebhook, + testSmtp, updateEmailDelivery, + updateGeneral, + updateSmtp, } from "./mutation" +import { + getEmailDelivery, + getGeneral, + getSmtp, + listApiKeys, + listWebhooks, +} from "./query" export const settingsRouter = router({ getSmtp: getSmtp, diff --git a/apps/backend/src/stats/query.ts b/apps/backend/src/stats/query.ts index f53c3f3..c4e3b52 100644 --- a/apps/backend/src/stats/query.ts +++ b/apps/backend/src/stats/query.ts @@ -1,14 +1,14 @@ -import { z } from "zod" -import { authProcedure } from "../trpc" -import { prisma } from "../utils/prisma" -import { subDays } from "date-fns" import { TRPCError } from "@trpc/server" -import { resolveProps } from "../utils/pProps" +import { subDays } from "date-fns" +import { z } from "zod" import { countDistinctRecipients, countDistinctRecipientsInTimeRange, } from "../../prisma/client/sql" +import { authProcedure } from "../trpc" import { messageStatus } from "../utils/message-status" +import { resolveProps } from "../utils/pProps" +import { prisma } from "../utils/prisma" export const getStats = authProcedure .input( diff --git a/apps/backend/src/subscriber/mutation.ts b/apps/backend/src/subscriber/mutation.ts index 345bb02..7388068 100644 --- a/apps/backend/src/subscriber/mutation.ts +++ b/apps/backend/src/subscriber/mutation.ts @@ -1,9 +1,9 @@ -import { z } from "zod" -import { authProcedure, publicProcedure } from "../trpc" -import { prisma } from "../utils/prisma" import { TRPCError } from "@trpc/server" import { parse } from "csv-parse" import { Readable } from "stream" +import { z } from "zod" +import { authProcedure, publicProcedure } from "../trpc" +import { prisma } from "../utils/prisma" const createSubscriberSchema = z.object({ email: z.string().email("Invalid email address"), diff --git a/apps/backend/src/subscriber/query.ts b/apps/backend/src/subscriber/query.ts index 61b169c..0f69341 100644 --- a/apps/backend/src/subscriber/query.ts +++ b/apps/backend/src/subscriber/query.ts @@ -1,10 +1,10 @@ +import { TRPCError } from "@trpc/server" import { z } from "zod" +import { Prisma } from "../../prisma/client" import { authProcedure } from "../trpc" +import { resolveProps } from "../utils/pProps" import { prisma } from "../utils/prisma" -import { TRPCError } from "@trpc/server" import { paginationSchema } from "../utils/schemas" -import { Prisma } from "../../prisma/client" -import { resolveProps } from "../utils/pProps" export const listSubscribers = authProcedure .input(z.object({ organizationId: z.string() }).merge(paginationSchema)) diff --git a/apps/backend/src/subscriber/router.ts b/apps/backend/src/subscriber/router.ts index fab81d0..8d90744 100644 --- a/apps/backend/src/subscriber/router.ts +++ b/apps/backend/src/subscriber/router.ts @@ -1,11 +1,11 @@ import { router } from "../trpc" import { createSubscriber, - updateSubscriber, deleteSubscriber, importSubscribers, publicUnsubscribe, unsubscribeToggle, + updateSubscriber, verifyEmail, } from "./mutation" import { getSubscriber, listSubscribers } from "./query" diff --git a/apps/backend/src/template/mutation.ts b/apps/backend/src/template/mutation.ts index 4e68332..17283fa 100644 --- a/apps/backend/src/template/mutation.ts +++ b/apps/backend/src/template/mutation.ts @@ -1,7 +1,7 @@ +import { TRPCError } from "@trpc/server" import { z } from "zod" import { authProcedure } from "../trpc" import { prisma } from "../utils/prisma" -import { TRPCError } from "@trpc/server" const contentSchema = z .string() diff --git a/apps/backend/src/template/query.ts b/apps/backend/src/template/query.ts index 571333c..e69de29 100644 --- a/apps/backend/src/template/query.ts +++ b/apps/backend/src/template/query.ts @@ -1,98 +0,0 @@ -import { z } from "zod" -import { authProcedure } from "../trpc" -import { prisma } from "../utils/prisma" -import { TRPCError } from "@trpc/server" -import { paginationSchema } from "../utils/schemas" -import { Prisma } from "../../prisma/client" - -export const listTemplates = authProcedure - .input(z.object({ organizationId: z.string() }).merge(paginationSchema)) - .query(async ({ ctx, input }) => { - const userOrganization = await prisma.userOrganization.findFirst({ - where: { - userId: ctx.user.id, - organizationId: input.organizationId, - }, - }) - - if (!userOrganization) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Organization not found", - }) - } - - const where: Prisma.TemplateWhereInput = { - organizationId: input.organizationId, - ...(input.search - ? { - OR: [ - { name: { contains: input.search, mode: "insensitive" } }, - { description: { contains: input.search, mode: "insensitive" } }, - ], - } - : {}), - } - - const [total, templates] = await Promise.all([ - prisma.template.count({ where }), - prisma.template.findMany({ - where, - orderBy: [{ createdAt: "desc" }, { id: "desc" }], - skip: (input.page - 1) * input.perPage, - take: input.perPage, - }), - ]) - - const totalPages = Math.ceil(total / input.perPage) - - return { - templates, - pagination: { - total, - totalPages, - page: input.page, - perPage: input.perPage, - hasMore: input.page < totalPages, - }, - } - }) - -export const getTemplate = authProcedure - .input( - z.object({ - id: z.string(), - organizationId: z.string(), - }) - ) - .query(async ({ ctx, input }) => { - const userOrganization = await prisma.userOrganization.findFirst({ - where: { - userId: ctx.user.id, - organizationId: input.organizationId, - }, - }) - - if (!userOrganization) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Organization not found", - }) - } - - const template = await prisma.template.findFirst({ - where: { - id: input.id, - organizationId: input.organizationId, - }, - }) - - if (!template) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Template not found", - }) - } - - return template - }) diff --git a/apps/backend/src/template/router.ts b/apps/backend/src/template/router.ts index 1a9aef3..72b318c 100644 --- a/apps/backend/src/template/router.ts +++ b/apps/backend/src/template/router.ts @@ -1,5 +1,5 @@ import { router } from "../trpc" -import { createTemplate, updateTemplate, deleteTemplate } from "./mutation" +import { createTemplate, deleteTemplate, updateTemplate } from "./mutation" import { getTemplate, listTemplates } from "./query" export const templateRouter = router({ diff --git a/apps/backend/src/trpc.ts b/apps/backend/src/trpc.ts index fb8d752..8f51345 100644 --- a/apps/backend/src/trpc.ts +++ b/apps/backend/src/trpc.ts @@ -1,9 +1,9 @@ -import { initTRPC, TRPCError } from "@trpc/server" +import { TRPCError, initTRPC } from "@trpc/server" import * as trpcExpress from "@trpc/server/adapters/express" +import SuperJSON from "superjson" import { verifyToken } from "./utils/auth" import { prisma } from "./utils/prisma" import { tokenPayloadSchema } from "./utils/token" -import SuperJSON from "superjson" interface User { id: string diff --git a/apps/backend/src/user/mutation.ts b/apps/backend/src/user/mutation.ts index 05e5593..4f6ebdb 100644 --- a/apps/backend/src/user/mutation.ts +++ b/apps/backend/src/user/mutation.ts @@ -1,8 +1,8 @@ +import { TRPCError } from "@trpc/server" import { z } from "zod" -import { publicProcedure, authProcedure } from "../trpc" -import { prisma } from "../utils/prisma" +import { authProcedure, publicProcedure } from "../trpc" import { comparePasswords, generateToken, hashPassword } from "../utils/auth" -import { TRPCError } from "@trpc/server" +import { prisma } from "../utils/prisma" const signUpSchema = z.object({ email: z.string().email().min(1, "Email is required"), diff --git a/apps/backend/src/user/router.ts b/apps/backend/src/user/router.ts index ece4c17..0ff6f32 100644 --- a/apps/backend/src/user/router.ts +++ b/apps/backend/src/user/router.ts @@ -1,6 +1,6 @@ import { router } from "../trpc" -import { login, signup, updateProfile, changePassword } from "./mutation" -import { me, isFirstUser } from "./query" +import { changePassword, login, signup, updateProfile } from "./mutation" +import { isFirstUser, me } from "./query" export const userRouter = router({ signup, diff --git a/apps/backend/src/utils/auth.ts b/apps/backend/src/utils/auth.ts index a9705a6..1437f96 100644 --- a/apps/backend/src/utils/auth.ts +++ b/apps/backend/src/utils/auth.ts @@ -1,5 +1,5 @@ -import jwt from "jsonwebtoken" import bcrypt from "bcryptjs" +import jwt from "jsonwebtoken" import { env } from "../constants" export async function hashPassword(password: string) { diff --git a/apps/backend/src/utils/message-id.test.ts b/apps/backend/src/utils/message-id.test.ts index eef8013..2a37d3a 100644 --- a/apps/backend/src/utils/message-id.test.ts +++ b/apps/backend/src/utils/message-id.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest" +import { describe, expect, it } from "vitest" import { stripAngleBrackets } from "./message-id" describe("stripAngleBrackets", () => { diff --git a/apps/backend/src/utils/placeholder-parser.test.ts b/apps/backend/src/utils/placeholder-parser.test.ts index 4e868bf..842da6c 100644 --- a/apps/backend/src/utils/placeholder-parser.test.ts +++ b/apps/backend/src/utils/placeholder-parser.test.ts @@ -1,5 +1,5 @@ +import { describe, expect, it } from "vitest" import { replacePlaceholders } from "./placeholder-parser" -import { describe, it, expect } from "vitest" describe("replacePlaceholders", () => { it("should replace a single placeholder", () => { diff --git a/apps/backend/src/webhook/authorization.ts b/apps/backend/src/webhook/authorization.ts index a17aa10..f7be870 100644 --- a/apps/backend/src/webhook/authorization.ts +++ b/apps/backend/src/webhook/authorization.ts @@ -1,6 +1,6 @@ import express from "express" -import { logger } from "../utils/logger" import { getQuickJS } from "quickjs-emscripten" +import { logger } from "../utils/logger" // Run authorization code in sandbox export async function runAuthorization( diff --git a/apps/backend/src/webhook/handler.ts b/apps/backend/src/webhook/handler.ts index 64fa285..99e18aa 100644 --- a/apps/backend/src/webhook/handler.ts +++ b/apps/backend/src/webhook/handler.ts @@ -1,9 +1,9 @@ import express from "express" -import { prisma } from "../utils/prisma" import { logger } from "../utils/logger" +import { prisma } from "../utils/prisma" import { runAuthorization } from "./authorization" -import { transformPayload } from "./transformer" import { processWebhookEvent } from "./processor" +import { transformPayload } from "./transformer" import { WebhookResult } from "./types" // TODO: Consider these improvements: diff --git a/apps/backend/src/webhook/mutation.ts b/apps/backend/src/webhook/mutation.ts index bbbc91e..54095f1 100644 --- a/apps/backend/src/webhook/mutation.ts +++ b/apps/backend/src/webhook/mutation.ts @@ -1,7 +1,7 @@ +import { TRPCError } from "@trpc/server" import { z } from "zod" import { authProcedure } from "../trpc" import { prisma } from "../utils/prisma" -import { TRPCError } from "@trpc/server" const webhookSchema = z.object({ name: z.string().min(1, "Name is required"), diff --git a/apps/backend/src/webhook/processor.ts b/apps/backend/src/webhook/processor.ts index 671c351..a060d9a 100644 --- a/apps/backend/src/webhook/processor.ts +++ b/apps/backend/src/webhook/processor.ts @@ -1,6 +1,6 @@ import { logger } from "../utils/logger" import { prisma } from "../utils/prisma" -import { WebhookEvent, EVENT_STATUS_MAP } from "./types" +import { EVENT_STATUS_MAP, WebhookEvent } from "./types" // Process webhook event and update message status export async function processWebhookEvent( diff --git a/apps/backend/src/webhook/query.ts b/apps/backend/src/webhook/query.ts index a916d6f..9425d4a 100644 --- a/apps/backend/src/webhook/query.ts +++ b/apps/backend/src/webhook/query.ts @@ -1,7 +1,7 @@ +import { TRPCError } from "@trpc/server" import { z } from "zod" import { authProcedure } from "../trpc" import { prisma } from "../utils/prisma" -import { TRPCError } from "@trpc/server" export const listWebhooks = authProcedure .input( diff --git a/apps/backend/src/webhook/router.ts b/apps/backend/src/webhook/router.ts index 422454f..1aa0f57 100644 --- a/apps/backend/src/webhook/router.ts +++ b/apps/backend/src/webhook/router.ts @@ -1,6 +1,6 @@ import { router } from "../trpc" -import { listWebhooks, getWebhook, getWebhookLogs } from "./query" -import { createWebhook, updateWebhook, deleteWebhook } from "./mutation" +import { createWebhook, deleteWebhook, updateWebhook } from "./mutation" +import { getWebhook, getWebhookLogs, listWebhooks } from "./query" export const webhookRouter = router({ list: listWebhooks, diff --git a/apps/backend/src/webhook/transformer.ts b/apps/backend/src/webhook/transformer.ts index 323ca5b..884456c 100644 --- a/apps/backend/src/webhook/transformer.ts +++ b/apps/backend/src/webhook/transformer.ts @@ -1,8 +1,8 @@ import express from "express" -import { logger } from "../utils/logger" -import { WebhookEventSchema, WebhookResult } from "./types" import { getQuickJS } from "quickjs-emscripten" import { env } from "../constants" +import { logger } from "../utils/logger" +import { WebhookEventSchema, WebhookResult } from "./types" // Transform webhook payload using custom code or default schema export async function transformPayload( diff --git a/apps/backend/tests/integration/api/auth.test.ts b/apps/backend/tests/integration/api/auth.test.ts index 9a47f70..d23a752 100644 --- a/apps/backend/tests/integration/api/auth.test.ts +++ b/apps/backend/tests/integration/api/auth.test.ts @@ -1,5 +1,5 @@ -import { describe, it, expect } from "vitest" import { request } from "@helpers/request" +import { describe, expect, it } from "vitest" describe("Auth", () => { it("Should return 401 without API Key", async () => { diff --git a/apps/backend/tests/integration/api/subscribers/create-subscriber.test.ts b/apps/backend/tests/integration/api/subscribers/create-subscriber.test.ts index 821feea..848fa2a 100644 --- a/apps/backend/tests/integration/api/subscribers/create-subscriber.test.ts +++ b/apps/backend/tests/integration/api/subscribers/create-subscriber.test.ts @@ -1,8 +1,8 @@ -import { describe, it, expect } from "vitest" -import { createUser } from "@helpers/user/user" import { request } from "@helpers/request" -import { createList } from "@tests/integration/helpers/list/list" +import { createUser } from "@helpers/user/user" import { prisma } from "@src/utils/prisma" +import { createList } from "@tests/integration/helpers/list/list" +import { describe, expect, it } from "vitest" describe("[POST] /api/subscribers", () => { it("should create a subscriber", async () => { diff --git a/apps/backend/tests/integration/api/subscribers/delete-subscriber.test.ts b/apps/backend/tests/integration/api/subscribers/delete-subscriber.test.ts index adb255b..5dd1399 100644 --- a/apps/backend/tests/integration/api/subscribers/delete-subscriber.test.ts +++ b/apps/backend/tests/integration/api/subscribers/delete-subscriber.test.ts @@ -1,8 +1,8 @@ -import { describe, it, expect } from "vitest" -import { createUser } from "@helpers/user/user" import { request } from "@helpers/request" -import { createList } from "@tests/integration/helpers/list/list" +import { createUser } from "@helpers/user/user" import { prisma } from "@src/utils/prisma" +import { createList } from "@tests/integration/helpers/list/list" +import { describe, expect, it } from "vitest" describe("[DELETE] /api/subscribers/:id", () => { it("should delete a subscriber", async () => { diff --git a/apps/backend/tests/integration/api/subscribers/get-subscriber.test.ts b/apps/backend/tests/integration/api/subscribers/get-subscriber.test.ts index bda3b6f..7769e6d 100644 --- a/apps/backend/tests/integration/api/subscribers/get-subscriber.test.ts +++ b/apps/backend/tests/integration/api/subscribers/get-subscriber.test.ts @@ -1,8 +1,8 @@ -import { describe, it, expect } from "vitest" -import { createUser } from "@helpers/user/user" import { request } from "@helpers/request" -import { createList } from "@tests/integration/helpers/list/list" +import { createUser } from "@helpers/user/user" import { prisma } from "@src/utils/prisma" +import { createList } from "@tests/integration/helpers/list/list" +import { describe, expect, it } from "vitest" describe("[GET] /api/subscribers/:id", () => { it("should get a subscriber by id", async () => { diff --git a/apps/backend/tests/integration/api/subscribers/get-subscribers.test.ts b/apps/backend/tests/integration/api/subscribers/get-subscribers.test.ts index 0a68f91..c843d9a 100644 --- a/apps/backend/tests/integration/api/subscribers/get-subscribers.test.ts +++ b/apps/backend/tests/integration/api/subscribers/get-subscribers.test.ts @@ -1,8 +1,8 @@ -import { describe, it, expect } from "vitest" -import { createUser } from "@helpers/user/user" import { request } from "@helpers/request" -import { createList } from "@tests/integration/helpers/list/list" +import { createUser } from "@helpers/user/user" import { prisma } from "@src/utils/prisma" +import { createList } from "@tests/integration/helpers/list/list" +import { describe, expect, it } from "vitest" describe("[GET] /api/subscribers", () => { it("should get subscribers with pagination", async () => { diff --git a/apps/backend/tests/integration/api/subscribers/update-subscriber.test.ts b/apps/backend/tests/integration/api/subscribers/update-subscriber.test.ts index 7662a51..7272a66 100644 --- a/apps/backend/tests/integration/api/subscribers/update-subscriber.test.ts +++ b/apps/backend/tests/integration/api/subscribers/update-subscriber.test.ts @@ -1,8 +1,8 @@ -import { describe, it, expect } from "vitest" -import { createUser } from "@helpers/user/user" import { request } from "@helpers/request" -import { createList } from "@tests/integration/helpers/list/list" +import { createUser } from "@helpers/user/user" import { prisma } from "@src/utils/prisma" +import { createList } from "@tests/integration/helpers/list/list" +import { describe, expect, it } from "vitest" describe("[PUT] /api/subscribers/:id", () => { it("should update subscriber details", async () => { diff --git a/apps/backend/tests/integration/helpers/setup.ts b/apps/backend/tests/integration/helpers/setup.ts index 12a2bc0..5e2a97b 100644 --- a/apps/backend/tests/integration/helpers/setup.ts +++ b/apps/backend/tests/integration/helpers/setup.ts @@ -1,7 +1,8 @@ +import { execSync } from "child_process" import { config } from "dotenv" -config({ path: ".env.test" }) import { beforeEach } from "vitest" -import { execSync } from "child_process" + +config({ path: ".env.test" }) execSync("prisma migrate deploy", { stdio: "ignore" }) diff --git a/apps/backend/tests/integration/helpers/user/user.ts b/apps/backend/tests/integration/helpers/user/user.ts index 7ec340b..d47fc6d 100644 --- a/apps/backend/tests/integration/helpers/user/user.ts +++ b/apps/backend/tests/integration/helpers/user/user.ts @@ -1,5 +1,5 @@ -import { hashPassword } from "@src/utils/auth" import { faker } from "@faker-js/faker" +import { hashPassword } from "@src/utils/auth" import { prisma } from "@src/utils/prisma" export async function createUser() { diff --git a/apps/backend/tests/integration/user.test.ts b/apps/backend/tests/integration/user.test.ts index 7001b5a..edef681 100644 --- a/apps/backend/tests/integration/user.test.ts +++ b/apps/backend/tests/integration/user.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest" +import { describe, expect, it } from "vitest" import { request } from "./helpers/request" describe("First test", () => { diff --git a/apps/backend/vitest.config.ts b/apps/backend/vitest.config.ts index 54f5de8..7f27d32 100644 --- a/apps/backend/vitest.config.ts +++ b/apps/backend/vitest.config.ts @@ -1,6 +1,6 @@ // vitest.config.integration.ts -import { defineConfig } from "vitest/config" import path from "path" +import { defineConfig } from "vitest/config" export default defineConfig({ test: { diff --git a/apps/docs/src/app/layout.tsx b/apps/docs/src/app/layout.tsx index a292327..bcdda38 100644 --- a/apps/docs/src/app/layout.tsx +++ b/apps/docs/src/app/layout.tsx @@ -1,12 +1,12 @@ -import "./globals.css" +import type { Metadata } from "next" +import Image from "next/image" +import Link from "next/link" import { Footer, Layout, Navbar } from "nextra-theme-docs" +import "nextra-theme-docs/style.css" import { Head } from "nextra/components" import { getPageMap } from "nextra/page-map" -import "nextra-theme-docs/style.css" -import Link from "next/link" -import Image from "next/image" -import type { Metadata } from "next" import { Analytics } from "@/components" +import "./globals.css" export const metadata: Metadata = { title: "LetterSpace Documentation | Self-hosted Newsletter Platform", diff --git a/apps/docs/src/mdx-components.tsx b/apps/docs/src/mdx-components.tsx index 1b9da9c..64f6212 100644 --- a/apps/docs/src/mdx-components.tsx +++ b/apps/docs/src/mdx-components.tsx @@ -1,5 +1,5 @@ -import "./app/globals.css" import { useMDXComponents as getThemeComponents } from "nextra-theme-docs" +import "./app/globals.css" import { WebhookEventsReference } from "./components" const themeComponents = getThemeComponents() diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json index 21a928b..b46d82c 100644 --- a/apps/docs/tsconfig.json +++ b/apps/docs/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "ES2017", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -25,9 +21,7 @@ } ], "paths": { - "@/*": [ - "./src/*" - ] + "@/*": ["./src/*"] } }, "include": [ @@ -38,7 +32,5 @@ ".next/types/**/*.ts", ".next/dev/types/**/*.ts" ], - "exclude": [ - "node_modules" - ] + "exclude": ["node_modules"] } diff --git a/apps/landing-page/next.config.ts b/apps/landing-page/next.config.ts index 635effe..eaf42ba 100644 --- a/apps/landing-page/next.config.ts +++ b/apps/landing-page/next.config.ts @@ -1,6 +1,6 @@ // To throw error if any env variable is not defined -import "./src/constants" import type { NextConfig } from "next" +import "./src/constants" const nextConfig: NextConfig = { /* config options here */ diff --git a/apps/landing-page/src/app/layout.tsx b/apps/landing-page/src/app/layout.tsx index b306f12..3ab2d9f 100644 --- a/apps/landing-page/src/app/layout.tsx +++ b/apps/landing-page/src/app/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata, Viewport } from "next" import { Geist, Geist_Mono } from "next/font/google" -import "./globals.css" import { Analytics } from "@/components/analytics" +import "./globals.css" const geistSans = Geist({ variable: "--font-geist-sans", diff --git a/apps/landing-page/src/app/page.tsx b/apps/landing-page/src/app/page.tsx index d916576..d28a760 100644 --- a/apps/landing-page/src/app/page.tsx +++ b/apps/landing-page/src/app/page.tsx @@ -1,7 +1,7 @@ -import { Header } from "@/components/header" -import { Hero } from "@/components/hero" import { Features, featuresData } from "@/components/features" import { Footer } from "@/components/footer" +import { Header } from "@/components/header" +import { Hero } from "@/components/hero" export default function Home() { return ( diff --git a/apps/landing-page/src/components/features.tsx b/apps/landing-page/src/components/features.tsx index 0f132eb..aa8e98b 100644 --- a/apps/landing-page/src/components/features.tsx +++ b/apps/landing-page/src/components/features.tsx @@ -3,14 +3,14 @@ import Link from "next/link" import type { LucideIcon } from "lucide-react" import { - Zap, - Wifi, + BugPlay, + ChevronDown, Database, - MessageSquare, FileCode, + MessageSquare, Settings, - BugPlay, - ChevronDown, + Wifi, + Zap, } from "lucide-react" import { constants } from "@/constants" diff --git a/apps/landing-page/src/components/footer.tsx b/apps/landing-page/src/components/footer.tsx index 14840f1..8ceb2c9 100644 --- a/apps/landing-page/src/components/footer.tsx +++ b/apps/landing-page/src/components/footer.tsx @@ -1,8 +1,8 @@ "use client" +import Image from "next/image" import Link from "next/link" import { constants } from "@/constants" -import Image from "next/image" export const Footer = () => (