mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
chore: Move welcome email to processor (#11939)
* chore: Move welcome email to processor * fix: Restore welcome email on invite acceptance
This commit is contained in:
@@ -130,6 +130,7 @@ export default class DeliverWebhookTask extends BaseTask<Props> {
|
||||
case "users.invite":
|
||||
case "users.promote":
|
||||
case "users.demote":
|
||||
case "users.invite_accepted":
|
||||
await this.handleUserEvent(subscription, event);
|
||||
return;
|
||||
case "documents.create":
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import WelcomeEmail from "@server/emails/templates/WelcomeEmail";
|
||||
import { TeamDomain } from "@server/models";
|
||||
import Collection from "@server/models/Collection";
|
||||
import UserAuthentication from "@server/models/UserAuthentication";
|
||||
@@ -15,7 +14,6 @@ describe("accountProvisioner", () => {
|
||||
|
||||
describe("hosted", () => {
|
||||
it("should create a new user and team", async () => {
|
||||
const spy = jest.spyOn(WelcomeEmail.prototype, "schedule");
|
||||
const email = faker.internet.email();
|
||||
const { user, team, isNewTeam, isNewUser } = await accountProvisioner(
|
||||
ctx,
|
||||
@@ -50,19 +48,15 @@ describe("accountProvisioner", () => {
|
||||
expect(user.email).toEqual(email);
|
||||
expect(isNewUser).toEqual(true);
|
||||
expect(isNewTeam).toEqual(true);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
const collectionCount = await Collection.count({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
},
|
||||
});
|
||||
expect(collectionCount).toEqual(1);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("should update exising user and authentication", async () => {
|
||||
const spy = jest.spyOn(WelcomeEmail.prototype, "schedule");
|
||||
it("should update existing user and authentication", async () => {
|
||||
const existingTeam = await buildTeam();
|
||||
const providers = await existingTeam.$get("authenticationProviders");
|
||||
const authenticationProvider = providers[0];
|
||||
@@ -100,9 +94,6 @@ describe("accountProvisioner", () => {
|
||||
expect(user.email).toEqual(newEmail);
|
||||
expect(isNewTeam).toEqual(false);
|
||||
expect(isNewUser).toEqual(false);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("should allow authentication by email matching", async () => {
|
||||
@@ -283,7 +274,6 @@ describe("accountProvisioner", () => {
|
||||
});
|
||||
|
||||
it("should create a new user in an existing team when the domain is allowed", async () => {
|
||||
const spy = jest.spyOn(WelcomeEmail.prototype, "schedule");
|
||||
const team = await buildTeam();
|
||||
const admin = await buildAdmin({ teamId: team.id });
|
||||
const authenticationProviders = await team.$get(
|
||||
@@ -324,7 +314,6 @@ describe("accountProvisioner", () => {
|
||||
expect(auth.scopes[0]).toEqual("read");
|
||||
expect(user.email).toEqual(email);
|
||||
expect(isNewUser).toEqual(true);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
// should provision welcome collection
|
||||
const collectionCount = await Collection.count({
|
||||
where: {
|
||||
@@ -332,12 +321,9 @@ describe("accountProvisioner", () => {
|
||||
},
|
||||
});
|
||||
expect(collectionCount).toEqual(1);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("should create a new user in an existing team", async () => {
|
||||
const spy = jest.spyOn(WelcomeEmail.prototype, "schedule");
|
||||
const team = await buildTeam();
|
||||
const authenticationProviders = await team.$get(
|
||||
"authenticationProviders"
|
||||
@@ -372,7 +358,6 @@ describe("accountProvisioner", () => {
|
||||
expect(auth.scopes[0]).toEqual("read");
|
||||
expect(user.email).toEqual(email);
|
||||
expect(isNewUser).toEqual(true);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
// should provision welcome collection
|
||||
const collectionCount = await Collection.count({
|
||||
where: {
|
||||
@@ -380,12 +365,9 @@ describe("accountProvisioner", () => {
|
||||
},
|
||||
});
|
||||
expect(collectionCount).toEqual(1);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("should handle emails with capital letters correctly", async () => {
|
||||
const spy = jest.spyOn(WelcomeEmail.prototype, "schedule");
|
||||
const email = "Jenny.Tester@EXAMPLE.COM";
|
||||
|
||||
const params = {
|
||||
@@ -418,7 +400,6 @@ describe("accountProvisioner", () => {
|
||||
expect(user.email).toEqual(email);
|
||||
expect(isNewUser).toEqual(true);
|
||||
expect(isNewTeam).toEqual(true);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
|
||||
// Test that we can find the user again
|
||||
const existing = await accountProvisioner(ctx, params);
|
||||
@@ -427,8 +408,6 @@ describe("accountProvisioner", () => {
|
||||
expect(existing.isNewTeam).toEqual(false);
|
||||
expect(existing.isNewUser).toEqual(false);
|
||||
expect(existing.user.id).toEqual(user.id);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("should allow connecting a new authentication provider while logged in", async () => {
|
||||
|
||||
@@ -2,7 +2,6 @@ import path from "node:path";
|
||||
import { readFile } from "fs-extra";
|
||||
import invariant from "invariant";
|
||||
import { CollectionPermission, UserRole } from "@shared/types";
|
||||
import WelcomeEmail from "@server/emails/templates/WelcomeEmail";
|
||||
import env from "@server/env";
|
||||
import {
|
||||
InvalidAuthenticationError,
|
||||
@@ -15,6 +14,7 @@ import {
|
||||
AuthenticationProvider,
|
||||
Collection,
|
||||
Document,
|
||||
Event,
|
||||
Team,
|
||||
} from "@server/models";
|
||||
import { DocumentHelper } from "@server/models/helpers/DocumentHelper";
|
||||
@@ -194,14 +194,11 @@ async function accountProvisioner(
|
||||
});
|
||||
const { isNewUser, user } = result;
|
||||
|
||||
// TODO: Move to processor
|
||||
if (isNewUser) {
|
||||
await new WelcomeEmail({
|
||||
to: user.email,
|
||||
language: user.language,
|
||||
role: user.role,
|
||||
teamUrl: team.url,
|
||||
}).schedule();
|
||||
if (isNewUser && user.isInvited) {
|
||||
await Event.createFromContext(ctx, {
|
||||
name: "users.invite_accepted",
|
||||
userId: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (isNewUser || isNewTeam) {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import WelcomeEmail from "@server/emails/templates/WelcomeEmail";
|
||||
import { Team, User } from "@server/models";
|
||||
import type { Event, UserEvent } from "@server/types";
|
||||
import BaseProcessor from "./BaseProcessor";
|
||||
|
||||
export default class UserCreatedProcessor extends BaseProcessor {
|
||||
static applicableEvents: Event["name"][] = [
|
||||
"users.create",
|
||||
"users.invite_accepted",
|
||||
];
|
||||
|
||||
async perform(event: UserEvent) {
|
||||
const [user, team] = await Promise.all([
|
||||
User.findByPk(event.userId, { rejectOnEmpty: true }),
|
||||
Team.findByPk(event.teamId, { rejectOnEmpty: true }),
|
||||
]);
|
||||
|
||||
// Invited users receive an InviteEmail at invite time, and a WelcomeEmail
|
||||
// when they accept the invite and sign in for the first time.
|
||||
if (event.name === "users.create" && user.isInvited) {
|
||||
return;
|
||||
}
|
||||
|
||||
await new WelcomeEmail({
|
||||
to: user.email,
|
||||
language: user.language,
|
||||
role: user.role,
|
||||
teamUrl: team.url,
|
||||
}).schedule();
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -162,7 +162,8 @@ export type UserEvent = BaseEvent<User> &
|
||||
| "users.update"
|
||||
| "users.suspend"
|
||||
| "users.activate"
|
||||
| "users.delete";
|
||||
| "users.delete"
|
||||
| "users.invite_accepted";
|
||||
userId: string;
|
||||
}
|
||||
| {
|
||||
|
||||
Reference in New Issue
Block a user