chore: Move to Typescript (#2783)

This PR moves the entire project to Typescript. Due to the ~1000 ignores this will lead to a messy codebase for a while, but the churn is worth it – all of those ignore comments are places that were never type-safe previously.

closes #1282
This commit is contained in:
Tom Moor
2021-11-29 06:40:55 -08:00
committed by GitHub
parent 25ccfb5d04
commit 15b1069bcc
1017 changed files with 17410 additions and 54942 deletions

View File

@@ -0,0 +1,188 @@
import Sequelize from "sequelize";
import { Event, Team, User, UserAuthentication } from "@server/models";
import { sequelize } from "../sequelize";
const Op = Sequelize.Op;
type UserCreatorResult = {
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User;
isNewUser: boolean;
// @ts-expect-error ts-migrate(2749) FIXME: 'UserAuthentication' refers to a value, but is bei... Remove this comment to see the full error message
authentication: UserAuthentication;
};
export default async function userCreator({
name,
email,
username,
isAdmin,
avatarUrl,
teamId,
authentication,
ip,
}: {
name: string;
email: string;
username?: string;
isAdmin?: boolean;
avatarUrl?: string;
teamId: string;
ip: string;
authentication: {
authenticationProviderId: string;
providerId: string;
scopes: string[];
accessToken?: string;
refreshToken?: string;
};
}): Promise<UserCreatorResult> {
const { authenticationProviderId, providerId, ...rest } = authentication;
const auth = await UserAuthentication.findOne({
where: {
providerId,
},
include: [
{
model: User,
as: "user",
},
],
});
// Someone has signed in with this authentication before, we just
// want to update the details instead of creating a new record
if (auth) {
const { user } = auth;
// We found an authentication record that matches the user id, but it's
// associated with a different authentication provider, (eg a different
// hosted google domain). This is possible in Google Auth when moving domains.
// In the future we may auto-migrate these.
if (auth.authenticationProviderId !== authenticationProviderId) {
throw new Error(
`User authentication ${providerId} already exists for ${auth.authenticationProviderId}, tried to assign to ${authenticationProviderId}`
);
}
if (user) {
await user.update({
email,
username,
});
await auth.update(rest);
return {
user,
authentication: auth,
isNewUser: false,
};
}
// We found an authentication record, but the associated user was deleted or
// otherwise didn't exist. Cleanup the auth record and proceed with creating
// a new user. See: https://github.com/outline/outline/issues/2022
await auth.destroy();
}
// A `user` record might exist in the form of an invite even if there is no
// existing authentication record that matches. In Outline an invite is a
// shell user record.
const invite = await User.findOne({
where: {
email,
teamId,
lastActiveAt: {
[Op.eq]: null,
},
},
include: [
{
model: UserAuthentication,
as: "authentications",
required: false,
},
],
});
// We have an existing invite for his user, so we need to update it with our
// new details and link up the authentication method
if (invite && !invite.authentications.length) {
const transaction = await sequelize.transaction();
let auth;
try {
await invite.update(
{
name,
avatarUrl,
},
{
transaction,
}
);
auth = await invite.createAuthentication(authentication, {
transaction,
});
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
return {
user: invite,
authentication: auth,
isNewUser: true,
};
}
// No auth, no user this is an entirely new sign in.
const transaction = await sequelize.transaction();
try {
const { defaultUserRole } = await Team.findByPk(teamId, {
attributes: ["defaultUserRole"],
transaction,
});
const user = await User.create(
{
name,
email,
username,
isAdmin: typeof isAdmin === "boolean" && isAdmin,
isViewer: isAdmin === true ? false : defaultUserRole === "viewer",
teamId,
avatarUrl,
service: null,
authentications: [authentication],
},
{
include: "authentications",
transaction,
}
);
await Event.create(
{
name: "users.create",
actorId: user.id,
userId: user.id,
teamId: user.teamId,
data: {
name: user.name,
},
ip,
},
{
transaction,
}
);
await transaction.commit();
return {
user,
authentication: user.authentications[0],
isNewUser: true,
};
} catch (err) {
await transaction.rollback();
throw err;
}
}