Files
outline/server/commands/teamCreator.ts
Tom Moor cc591ebc66 Conversion of User to event system (#9741)
* Conversion of User to event system

* fix

* warning

* fixes

* Skip lastActiveAt in changeset

* fix: Skip count in view changeset

* refactor: Remove userDestroyer

* refactor: Remove userSuspender

* refactor: Remove userUnsuspender

* tests
2025-07-27 13:15:21 -04:00

93 lines
2.4 KiB
TypeScript

import { InferCreationAttributes } from "sequelize";
import slugify from "slugify";
import { RESERVED_SUBDOMAINS } from "@shared/utils/domains";
import { traceFunction } from "@server/logging/tracing";
import { Team } from "@server/models";
import { APIContext } from "@server/types";
type Props = {
/** The displayed name of the team */
name: string;
/** The domain name from the email of the user logging in */
domain?: string;
/** The preferred subdomain to provision for the team if not yet created */
subdomain: string;
/** The public url of an image representing the team */
avatarUrl?: string | null;
/** Details of the authentication provider being used */
authenticationProviders: {
/** The name of the authentication provider, eg "google" */
name: string;
/** External identifier of the authentication provider */
providerId: string;
}[];
};
async function teamCreator(
ctx: APIContext,
{ name, subdomain, avatarUrl, authenticationProviders }: Props
): Promise<Team> {
if (!avatarUrl?.startsWith("http")) {
avatarUrl = null;
}
const team = await Team.createWithCtx(
ctx,
{
name,
avatarUrl,
authenticationProviders,
} as Partial<InferCreationAttributes<Team>>,
undefined,
{
include: ["authenticationProviders"],
}
);
const availableSubdomain = await findAvailableSubdomain(team, subdomain);
await team.update(
{ subdomain: availableSubdomain },
{
transaction: ctx.context.transaction,
}
);
return team;
}
async function findAvailableSubdomain(team: Team, requestedSubdomain: string) {
// filter subdomain to only valid characters
// if there are less than the minimum length, use a default subdomain
const normalizedSubdomain = slugify(requestedSubdomain, {
lower: true,
strict: true,
});
let subdomain =
normalizedSubdomain.length < 3 ||
RESERVED_SUBDOMAINS.includes(normalizedSubdomain)
? "team"
: normalizedSubdomain;
let append = 0;
for (;;) {
const existing = await Team.findOne({
where: { subdomain },
paranoid: false,
});
if (existing) {
// subdomain was invalid or already used, try another
subdomain = `${normalizedSubdomain}${++append}`;
} else {
break;
}
}
return subdomain;
}
export default traceFunction({
spanName: "teamCreator",
})(teamCreator);