enable teams for self-hosted (#137)

* enable teams for self-hosted

* remove console
This commit is contained in:
KM Koushik
2025-03-29 00:56:06 +11:00
committed by GitHub
parent 1b6676c1b1
commit f1186f875c
12 changed files with 214 additions and 98 deletions

View File

@@ -5,64 +5,31 @@ import { env } from "~/env";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
export const invitationRouter = createTRPCRouter({
createTeam: protectedProcedure
.input(z.object({ name: z.string() }))
.mutation(async ({ ctx, input }) => {
const teams = await ctx.db.team.findMany({
getUserInvites: protectedProcedure
.input(
z.object({
inviteId: z.string().optional().nullable(),
})
)
.query(async ({ ctx, input }) => {
if (!ctx.session.user.email) {
return [];
}
const invites = await ctx.db.teamInvite.findMany({
where: {
teamUsers: {
some: {
userId: ctx.session.user.id,
},
},
...(input.inviteId
? { id: input.inviteId }
: { email: ctx.session.user.email }),
},
include: {
team: true,
},
});
if (teams.length > 0) {
console.log("User already has a team");
return;
}
if (!env.NEXT_PUBLIC_IS_CLOUD) {
const _team = await ctx.db.team.findFirst();
if (_team) {
throw new TRPCError({
message: "Can't have multiple teams in self hosted version",
code: "UNAUTHORIZED",
});
}
}
return ctx.db.team.create({
data: {
name: input.name,
teamUsers: {
create: {
userId: ctx.session.user.id,
role: "ADMIN",
},
},
},
});
return invites;
}),
getUserInvites: protectedProcedure.query(async ({ ctx }) => {
if (!ctx.session.user.email) {
return [];
}
const invites = await ctx.db.teamInvite.findMany({
where: {
email: ctx.session.user.email,
},
include: {
team: true,
},
});
return invites;
}),
getInvite: protectedProcedure
.input(z.object({ inviteId: z.string() }))
.query(async ({ ctx, input }) => {

View File

@@ -9,6 +9,7 @@ import {
teamAdminProcedure,
} from "~/server/api/trpc";
import { sendTeamInviteEmail } from "~/server/mailer";
import send from "~/server/public-api/api/emails/send-email";
export const teamRouter = createTRPCRouter({
createTeam: protectedProcedure
@@ -97,8 +98,21 @@ export const teamRouter = createTRPCRouter({
}),
createTeamInvite: teamAdminProcedure
.input(z.object({ email: z.string(), role: z.enum(["MEMBER", "ADMIN"]) }))
.input(
z.object({
email: z.string(),
role: z.enum(["MEMBER", "ADMIN"]),
sendEmail: z.boolean().default(true),
})
)
.mutation(async ({ ctx, input }) => {
if (!input.email) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Email is required",
});
}
const user = await ctx.db.user.findUnique({
where: {
email: input.email,
@@ -125,7 +139,9 @@ export const teamRouter = createTRPCRouter({
const teamUrl = `${env.NEXTAUTH_URL}/join-team?inviteId=${teamInvite.id}`;
await sendTeamInviteEmail(input.email, teamUrl, ctx.team.name);
if (input.sendEmail) {
await sendTeamInviteEmail(input.email, teamUrl, ctx.team.name);
}
return teamInvite;
}),

View File

@@ -1,5 +1,9 @@
import { env } from "~/env";
import { Unsend } from "unsend";
import { isSelfHosted } from "~/utils/common";
import { db } from "./db";
import { getDomains } from "./service/domain-service";
import { sendEmail } from "./service/email-service";
let unsend: Unsend | undefined;
@@ -54,7 +58,37 @@ async function sendMail(
text: string,
html: string
) {
if (env.UNSEND_API_KEY && env.FROM_EMAIL) {
if (isSelfHosted()) {
console.log("Sending email using self hosted");
/*
Self hosted so checking if we can send using one of the available domain
Assuming self hosted will have only one team
TODO: fix this
*/
const team = await db.team.findFirst({});
if (!team) {
console.error("No team found");
return;
}
const domains = await getDomains(team.id);
if (domains.length === 0 || !domains[0]) {
console.error("No domains found");
return;
}
const domain = domains[0];
await sendEmail({
teamId: team.id,
to: email,
from: `hello@${domain.name}`,
subject,
text,
html,
});
} else if (env.UNSEND_API_KEY && env.FROM_EMAIL) {
const resp = await getClient().emails.send({
to: email,
from: env.FROM_EMAIL,

View File

@@ -165,6 +165,17 @@ export async function deleteDomain(id: number) {
});
}
export async function getDomains(teamId: number) {
return db.domain.findMany({
where: {
teamId,
},
orderBy: {
createdAt: "desc",
},
});
}
async function getDmarcRecord(domain: string) {
try {
const dmarcRecord = await dnsResolveTxt(`_dmarc.${domain}`);

View File

@@ -201,6 +201,8 @@ async function executeEmail(
? JSON.parse(email.attachments)
: [];
console.log(`Domain: ${JSON.stringify(domain)}`);
const configurationSetName = await getConfigurationSetName(
domain?.clickTracking ?? false,
domain?.openTracking ?? false,
@@ -208,7 +210,6 @@ async function executeEmail(
);
if (!configurationSetName) {
console.log(`[EmailQueueService]: Configuration set not found, skipping`);
return;
}

View File

@@ -27,6 +27,7 @@ export class SesSettingsService {
region = env.AWS_DEFAULT_REGION
): Promise<SesSetting | null> {
await this.checkInitialized();
if (this.cache[region]) {
return this.cache[region] as SesSetting;
}