fixes
This commit is contained in:
@@ -34,9 +34,7 @@ export const apiRouter = createTRPCRouter({
|
|||||||
return keys;
|
return keys;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
deleteApiKey: teamProcedure
|
deleteApiKey: apiKeyProcedure.mutation(async ({ input }) => {
|
||||||
.input(z.object({ id: z.number() }))
|
return deleteApiKey(input.id);
|
||||||
.mutation(async ({ input }) => {
|
}),
|
||||||
return deleteApiKey(input.id);
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
@@ -4,6 +4,7 @@ import {
|
|||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
teamProcedure,
|
teamProcedure,
|
||||||
protectedProcedure,
|
protectedProcedure,
|
||||||
|
domainProcedure,
|
||||||
} from "~/server/api/trpc";
|
} from "~/server/api/trpc";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
import {
|
import {
|
||||||
@@ -27,14 +28,12 @@ export const domainRouter = createTRPCRouter({
|
|||||||
return createDomain(ctx.team.id, input.name, input.region);
|
return createDomain(ctx.team.id, input.name, input.region);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
startVerification: teamProcedure
|
startVerification: domainProcedure.mutation(async ({ ctx, input }) => {
|
||||||
.input(z.object({ id: z.number() }))
|
await ctx.db.domain.update({
|
||||||
.mutation(async ({ ctx, input }) => {
|
where: { id: input.id },
|
||||||
await ctx.db.domain.update({
|
data: { isVerifying: true },
|
||||||
where: { id: input.id },
|
});
|
||||||
data: { isVerifying: true },
|
}),
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
domains: teamProcedure.query(async ({ ctx }) => {
|
domains: teamProcedure.query(async ({ ctx }) => {
|
||||||
const domains = await db.domain.findMany({
|
const domains = await db.domain.findMany({
|
||||||
@@ -49,16 +48,13 @@ export const domainRouter = createTRPCRouter({
|
|||||||
return domains;
|
return domains;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getDomain: teamProcedure
|
getDomain: domainProcedure.query(async ({ input }) => {
|
||||||
.input(z.object({ id: z.number() }))
|
return getDomain(input.id);
|
||||||
.query(async ({ input }) => {
|
}),
|
||||||
return getDomain(input.id);
|
|
||||||
}),
|
|
||||||
|
|
||||||
updateDomain: teamProcedure
|
updateDomain: domainProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.number(),
|
|
||||||
clickTracking: z.boolean().optional(),
|
clickTracking: z.boolean().optional(),
|
||||||
openTracking: z.boolean().optional(),
|
openTracking: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
@@ -70,43 +66,39 @@ export const domainRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
deleteDomain: teamProcedure
|
deleteDomain: domainProcedure.mutation(async ({ input }) => {
|
||||||
.input(z.object({ id: z.number() }))
|
await deleteDomain(input.id);
|
||||||
.mutation(async ({ input }) => {
|
return { success: true };
|
||||||
await deleteDomain(input.id);
|
}),
|
||||||
return { success: true };
|
|
||||||
}),
|
|
||||||
|
|
||||||
sendTestEmailFromDomain: teamProcedure
|
sendTestEmailFromDomain: domainProcedure.mutation(
|
||||||
.input(z.object({ id: z.number() }))
|
async ({
|
||||||
.mutation(
|
ctx: {
|
||||||
async ({
|
session: { user },
|
||||||
ctx: {
|
team,
|
||||||
session: { user },
|
},
|
||||||
team,
|
input,
|
||||||
},
|
}) => {
|
||||||
input,
|
const domain = await db.domain.findFirst({
|
||||||
}) => {
|
where: { id: input.id, teamId: team.id },
|
||||||
const domain = await db.domain.findFirst({
|
});
|
||||||
where: { id: input.id, teamId: team.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
throw new Error("Domain not found");
|
throw new Error("Domain not found");
|
||||||
}
|
|
||||||
|
|
||||||
if (!user.email) {
|
|
||||||
throw new Error("User email not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return sendEmail({
|
|
||||||
teamId: team.id,
|
|
||||||
to: user.email,
|
|
||||||
from: `hello@${domain.name}`,
|
|
||||||
subject: "Unsend test email",
|
|
||||||
text: "hello,\n\nUnsend is the best open source sending platform\n\ncheck out https://unsend.dev",
|
|
||||||
html: "<p>hello,</p><p>Unsend is the best open source sending platform<p><p>check out <a href='https://unsend.dev'>unsend.dev</a>",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
),
|
|
||||||
|
if (!user.email) {
|
||||||
|
throw new Error("User email not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendEmail({
|
||||||
|
teamId: team.id,
|
||||||
|
to: user.email,
|
||||||
|
from: `hello@${domain.name}`,
|
||||||
|
subject: "Unsend test email",
|
||||||
|
text: "hello,\n\nUnsend is the best open source sending platform\n\ncheck out https://unsend.dev",
|
||||||
|
html: "<p>hello,</p><p>Unsend is the best open source sending platform<p><p>check out <a href='https://unsend.dev'>unsend.dev</a>",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
@@ -2,7 +2,11 @@ import { EmailStatus } from "@prisma/client";
|
|||||||
import { format, subDays } from "date-fns";
|
import { format, subDays } from "date-fns";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createTRPCRouter, teamProcedure } from "~/server/api/trpc";
|
import {
|
||||||
|
createTRPCRouter,
|
||||||
|
emailProcedure,
|
||||||
|
teamProcedure,
|
||||||
|
} from "~/server/api/trpc";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
import { cancelEmail, updateEmail } from "~/server/service/email-service";
|
import { cancelEmail, updateEmail } from "~/server/service/email-service";
|
||||||
|
|
||||||
@@ -167,43 +171,39 @@ export const emailRouter = createTRPCRouter({
|
|||||||
return { emailStatusCounts, totalCount, emailDailyStatusCounts };
|
return { emailStatusCounts, totalCount, emailDailyStatusCounts };
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getEmail: teamProcedure
|
getEmail: emailProcedure.query(async ({ input }) => {
|
||||||
.input(z.object({ id: z.string() }))
|
const email = await db.email.findUnique({
|
||||||
.query(async ({ input }) => {
|
where: {
|
||||||
const email = await db.email.findUnique({
|
id: input.id,
|
||||||
where: {
|
},
|
||||||
id: input.id,
|
select: {
|
||||||
},
|
emailEvents: {
|
||||||
select: {
|
orderBy: {
|
||||||
emailEvents: {
|
status: "desc",
|
||||||
orderBy: {
|
|
||||||
status: "desc",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
id: true,
|
|
||||||
createdAt: true,
|
|
||||||
latestStatus: true,
|
|
||||||
subject: true,
|
|
||||||
to: true,
|
|
||||||
from: true,
|
|
||||||
domainId: true,
|
|
||||||
text: true,
|
|
||||||
html: true,
|
|
||||||
scheduledAt: true,
|
|
||||||
},
|
},
|
||||||
});
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
latestStatus: true,
|
||||||
|
subject: true,
|
||||||
|
to: true,
|
||||||
|
from: true,
|
||||||
|
domainId: true,
|
||||||
|
text: true,
|
||||||
|
html: true,
|
||||||
|
scheduledAt: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return email;
|
return email;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
cancelEmail: teamProcedure
|
cancelEmail: emailProcedure.mutation(async ({ input }) => {
|
||||||
.input(z.object({ id: z.string() }))
|
await cancelEmail(input.id);
|
||||||
.mutation(async ({ input }) => {
|
}),
|
||||||
await cancelEmail(input.id);
|
|
||||||
}),
|
|
||||||
|
|
||||||
updateEmailScheduledAt: teamProcedure
|
updateEmailScheduledAt: emailProcedure
|
||||||
.input(z.object({ id: z.string(), scheduledAt: z.string().datetime() }))
|
.input(z.object({ scheduledAt: z.string().datetime() }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
await updateEmail(input.id, { scheduledAt: input.scheduledAt });
|
await updateEmail(input.id, { scheduledAt: input.scheduledAt });
|
||||||
}),
|
}),
|
||||||
|
@@ -125,6 +125,45 @@ export const teamProcedure = protectedProcedure.use(async ({ ctx, next }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const domainProcedure = teamProcedure
|
||||||
|
.input(z.object({ id: z.number() }))
|
||||||
|
.use(async ({ ctx, next, input }) => {
|
||||||
|
const domain = await db.domain.findUnique({
|
||||||
|
where: { id: input.id, teamId: ctx.team.id },
|
||||||
|
});
|
||||||
|
if (!domain) {
|
||||||
|
throw new TRPCError({ code: "NOT_FOUND", message: "Domain not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return next({ ctx: { ...ctx, domain } });
|
||||||
|
});
|
||||||
|
|
||||||
|
export const emailProcedure = teamProcedure
|
||||||
|
.input(z.object({ id: z.string() }))
|
||||||
|
.use(async ({ ctx, next, input }) => {
|
||||||
|
const email = await db.email.findUnique({
|
||||||
|
where: { id: input.id, teamId: ctx.team.id },
|
||||||
|
});
|
||||||
|
if (!email) {
|
||||||
|
throw new TRPCError({ code: "NOT_FOUND", message: "Email not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return next({ ctx: { ...ctx, email } });
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiKeyProcedure = teamProcedure
|
||||||
|
.input(z.object({ id: z.number() }))
|
||||||
|
.use(async ({ ctx, next, input }) => {
|
||||||
|
const apiKey = await db.apiKey.findUnique({
|
||||||
|
where: { id: input.id, teamId: ctx.team.id },
|
||||||
|
});
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new TRPCError({ code: "NOT_FOUND", message: "API key not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return next({ ctx: { ...ctx, apiKey } });
|
||||||
|
});
|
||||||
|
|
||||||
export const contactBookProcedure = teamProcedure
|
export const contactBookProcedure = teamProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
@@ -133,7 +172,7 @@ export const contactBookProcedure = teamProcedure
|
|||||||
)
|
)
|
||||||
.use(async ({ ctx, next, input }) => {
|
.use(async ({ ctx, next, input }) => {
|
||||||
const contactBook = await db.contactBook.findUnique({
|
const contactBook = await db.contactBook.findUnique({
|
||||||
where: { id: input.contactBookId },
|
where: { id: input.contactBookId, teamId: ctx.team.id },
|
||||||
});
|
});
|
||||||
if (!contactBook) {
|
if (!contactBook) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -142,13 +181,6 @@ export const contactBookProcedure = teamProcedure
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contactBook.teamId !== ctx.team.id) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this contact book",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return next({ ctx: { ...ctx, contactBook } });
|
return next({ ctx: { ...ctx, contactBook } });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -160,7 +192,7 @@ export const campaignProcedure = teamProcedure
|
|||||||
)
|
)
|
||||||
.use(async ({ ctx, next, input }) => {
|
.use(async ({ ctx, next, input }) => {
|
||||||
const campaign = await db.campaign.findUnique({
|
const campaign = await db.campaign.findUnique({
|
||||||
where: { id: input.campaignId },
|
where: { id: input.campaignId, teamId: ctx.team.id },
|
||||||
});
|
});
|
||||||
if (!campaign) {
|
if (!campaign) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -169,13 +201,6 @@ export const campaignProcedure = teamProcedure
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (campaign.teamId !== ctx.team.id) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this campaign",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return next({ ctx: { ...ctx, campaign } });
|
return next({ ctx: { ...ctx, campaign } });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -25,3 +25,11 @@ export const getContactBook = async (c: Context, teamId: number) => {
|
|||||||
|
|
||||||
return contactBook;
|
return contactBook;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const checkIsValidEmailId = async (emailId: string, teamId: number) => {
|
||||||
|
const email = await db.email.findUnique({ where: { id: emailId, teamId } });
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
throw new UnsendApiError({ code: "NOT_FOUND", message: "Email not found" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@@ -2,6 +2,7 @@ import { createRoute, z } from "@hono/zod-openapi";
|
|||||||
import { PublicAPIApp } from "~/server/public-api/hono";
|
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||||
import { getTeamFromToken } from "~/server/public-api/auth";
|
import { getTeamFromToken } from "~/server/public-api/auth";
|
||||||
import { cancelEmail } from "~/server/service/email-service";
|
import { cancelEmail } from "~/server/service/email-service";
|
||||||
|
import { checkIsValidEmailId } from "../../api-utils";
|
||||||
|
|
||||||
const route = createRoute({
|
const route = createRoute({
|
||||||
method: "post",
|
method: "post",
|
||||||
@@ -34,8 +35,9 @@ const route = createRoute({
|
|||||||
|
|
||||||
function cancelScheduledEmail(app: PublicAPIApp) {
|
function cancelScheduledEmail(app: PublicAPIApp) {
|
||||||
app.openapi(route, async (c) => {
|
app.openapi(route, async (c) => {
|
||||||
await getTeamFromToken(c);
|
const team = await getTeamFromToken(c);
|
||||||
const emailId = c.req.param("emailId");
|
const emailId = c.req.param("emailId");
|
||||||
|
await checkIsValidEmailId(emailId, team.id);
|
||||||
|
|
||||||
await cancelEmail(emailId);
|
await cancelEmail(emailId);
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@ import { createRoute, z } from "@hono/zod-openapi";
|
|||||||
import { PublicAPIApp } from "~/server/public-api/hono";
|
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||||
import { getTeamFromToken } from "~/server/public-api/auth";
|
import { getTeamFromToken } from "~/server/public-api/auth";
|
||||||
import { updateEmail } from "~/server/service/email-service";
|
import { updateEmail } from "~/server/service/email-service";
|
||||||
|
import { checkIsValidEmailId } from "../../api-utils";
|
||||||
|
|
||||||
const route = createRoute({
|
const route = createRoute({
|
||||||
method: "patch",
|
method: "patch",
|
||||||
@@ -44,9 +45,11 @@ const route = createRoute({
|
|||||||
|
|
||||||
function updateEmailScheduledAt(app: PublicAPIApp) {
|
function updateEmailScheduledAt(app: PublicAPIApp) {
|
||||||
app.openapi(route, async (c) => {
|
app.openapi(route, async (c) => {
|
||||||
await getTeamFromToken(c);
|
const team = await getTeamFromToken(c);
|
||||||
const emailId = c.req.param("emailId");
|
const emailId = c.req.param("emailId");
|
||||||
|
|
||||||
|
await checkIsValidEmailId(emailId, team.id);
|
||||||
|
|
||||||
await updateEmail(emailId, {
|
await updateEmail(emailId, {
|
||||||
scheduledAt: c.req.valid("json").scheduledAt,
|
scheduledAt: c.req.valid("json").scheduledAt,
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user