Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix add staff #309

Merged
merged 8 commits into from
Apr 22, 2024
94 changes: 94 additions & 0 deletions src/backend/lib/db_helpers/case_manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Env } from "@/backend/lib/types";
import { KyselyDatabaseInstance } from "@/backend/lib";
import { getTransporter } from "@/backend/lib/nodemailer";
import { user } from "zapatos/schema";

interface paraInputProps {
first_name: string;
last_name: string;
email: string;
}

/**
* Checks for the existence of a user with the given email, if
* they do not exist, create the user with the role of "staff",
* initiate email sending without awaiting result
*/
export async function createPara(
para: paraInputProps,
db: KyselyDatabaseInstance,
case_manager_id: string,
from_email: string,
to_email: string,
env: Env
): Promise<user.Selectable | undefined> {
const { first_name, last_name, email } = para;

let paraData = await db
.selectFrom("user")
.where("email", "=", email.toLowerCase())
.selectAll()
.executeTakeFirst();

if (!paraData) {
paraData = await db
.insertInto("user")
.values({
first_name,
last_name,
email: email.toLowerCase(),
role: "staff",
})
.returningAll()
.executeTakeFirst();

// promise, will not interfere with returning paraData
void sendInviteEmail(
from_email,
to_email,
first_name,
case_manager_id,
env
);
}

return paraData;
}

/**
* Sends an invitation email to a para
*/
export async function sendInviteEmail(
fromEmail: string,
toEmail: string,
first_name: string,
caseManagerName: string,
env: Env
): Promise<void> {
await getTransporter(env).sendMail({
from: fromEmail,
to: toEmail,
subject: "Para-professional email confirmation",
text: "Email confirmation",
html: `<p>Dear ${first_name},</p><p>Welcome to the data collection team for SFUSD.EDU!</p><p>I am writing to invite you to join our data collection efforts for our students. We are using an online platform called <strong>Project Compass</strong> to track and monitor student progress, and your participation is crucial to the success of this initiative.</p><p>To access Project Compass and begin collecting data, please follow these steps:</p><ul><li>Go to the website: (<a href="https://staging.compassiep.com/">https://staging.compassiep.com/</a>)</li> <li>Login using your provided username and password</li><li>Once logged in, navigate to the dashboard where you would see the student goals page</li></ul><p>By clicking on the <strong>data collection</strong> button, you will be directed to the instructions outlining the necessary steps for data collection. Simply follow the provided instructions and enter the required data points accurately.</p><p>If you encounter any difficulties or have any questions, please feel free to reach out to me. I am here to assist you throughout the process and ensure a smooth data collection experience. Your dedication and contribution will make a meaningful impact on our students' educational journeys.</p><p>Thank you,</p><p>${caseManagerName}<br>Case Manager</p>`,
});
return;
}

/**
* Takes a given user (para), another user (cm), and a Kysely database,
* creates a link between the users in the database's
* "paras_assigned_to_case_manager" table
*/
export async function assignParaToCaseManager(
para_id: string,
case_manager_id: string,
db: KyselyDatabaseInstance
): Promise<void> {
await db
.insertInto("paras_assigned_to_case_manager")
.values({ case_manager_id, para_id })
.execute();

return;
}
25 changes: 25 additions & 0 deletions src/backend/routers/case_manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,31 @@ test("getMyParas", async (t) => {
t.is(myParas.length, 1);
});

test("addStaff", async (t) => {
const { trpc } = await getTestServer(t, {
authenticateAs: "case_manager",
});

const parasBeforeAdd = await trpc.case_manager.getMyParas.query();
t.is(parasBeforeAdd.length, 0);

const newParaData = {
first_name: "Staffy",
last_name: "Para",
email: "[email protected]",
};

await trpc.case_manager.addStaff.mutate(newParaData);

const parasAfterAdd = await trpc.case_manager.getMyParas.query();
t.is(parasAfterAdd.length, 1);

const createdPara = parasAfterAdd[0];
t.is(createdPara.first_name, newParaData.first_name);
t.is(createdPara.last_name, newParaData.last_name);
t.is(createdPara.email, newParaData.email);
});

test("addPara", async (t) => {
const { trpc, seed } = await getTestServer(t, {
authenticateAs: "case_manager",
Expand Down
49 changes: 42 additions & 7 deletions src/backend/routers/case_manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { z } from "zod";
import { authenticatedProcedure, router } from "../trpc";
import {
createPara,
assignParaToCaseManager,
} from "../lib/db_helpers/case_manager";

export const case_manager = router({
/**
Expand Down Expand Up @@ -156,20 +160,51 @@ export const case_manager = router({
return result;
}),

/**
* Handles creation of para and assignment to user, attempts to send
* email but does not await email success
*/
addStaff: authenticatedProcedure
.input(
z.object({
first_name: z.string(),
last_name: z.string(),
email: z.string().email(),
})
)
.mutation(async (req) => {
const para = await createPara(
req.input,
req.ctx.db,
req.ctx.auth.userId,
req.ctx.env.EMAIL,
req.input.email,
req.ctx.env
);

return await assignParaToCaseManager(
para?.user_id || "",
req.ctx.auth.userId,
req.ctx.db
);
}),

/**
* Deprecated: use addStaff instead
*/
addPara: authenticatedProcedure
.input(
z.object({
para_id: z.string(),
})
)
.mutation(async (req) => {
const { para_id } = req.input;
const { userId } = req.ctx.auth;

await req.ctx.db
.insertInto("paras_assigned_to_case_manager")
.values({ case_manager_id: userId, para_id })
.execute();
await assignParaToCaseManager(
req.input.para_id,
req.ctx.auth.userId,
req.ctx.db
);
return;
}),

editPara: authenticatedProcedure
Expand Down
47 changes: 16 additions & 31 deletions src/backend/routers/para.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { z } from "zod";
import { getTransporter } from "../lib/nodemailer";

Check warning on line 2 in src/backend/routers/para.ts

View workflow job for this annotation

GitHub Actions / lint

'getTransporter' is defined but never used
import { authenticatedProcedure, router } from "../trpc";
import { createPara } from "../lib/db_helpers/case_manager";

export const para = router({
getParaById: authenticatedProcedure
Expand Down Expand Up @@ -31,6 +32,9 @@
return result;
}),

/**
* Deprecated: use case_manager.addStaff instead
*/
createPara: authenticatedProcedure
.input(
z.object({
Expand All @@ -40,41 +44,22 @@
})
)
.mutation(async (req) => {
const { first_name, last_name, email } = req.input;

Check warning on line 47 in src/backend/routers/para.ts

View workflow job for this annotation

GitHub Actions / lint

'first_name' is assigned a value but never used

Check warning on line 47 in src/backend/routers/para.ts

View workflow job for this annotation

GitHub Actions / lint

'last_name' is assigned a value but never used

let paraData = await req.ctx.db
.selectFrom("user")
.where("email", "=", email.toLowerCase())
.selectAll()
.executeTakeFirst();

const caseManagerName = req.ctx.auth.session.user?.name ?? "";

if (!paraData) {
paraData = await req.ctx.db
.insertInto("user")
.values({
first_name,
last_name,
email: email.toLowerCase(),
role: "staff",
})
.returningAll()
.executeTakeFirst();
const para = await createPara(
req.input,
req.ctx.db,
req.ctx.auth.session.user?.name ?? "",
req.ctx.env.EMAIL,
email,
req.ctx.env
);

// TODO: Logic for sending email to staff. Should email be sent everytime or only first time? Should staff be notified that they are added to a certain case manager's list?
await getTransporter(req.ctx.env).sendMail({
from: req.ctx.env.EMAIL,
to: email,
subject: "Para-professional email confirmation",
text: "Email confirmation",
html: `<p>Dear ${first_name},</p><p>Welcome to the data collection team for SFUSD.EDU!</p><p>I am writing to invite you to join our data collection efforts for our students. We are using an online platform called <strong>Project Compass</strong> to track and monitor student progress, and your participation is crucial to the success of this initiative.</p><p>To access Project Compass and begin collecting data, please follow these steps:</p><ul><li>Go to the website: (<a href="https://staging.compassiep.com/">https://staging.compassiep.com/</a>)</li> <li>Login using your provided username and password</li><li>Once logged in, navigate to the dashboard where you would see the student goals page</li></ul><p>By clicking on the <strong>data collection</strong> button, you will be directed to the instructions outlining the necessary steps for data collection. Simply follow the provided instructions and enter the required data points accurately.</p><p>If you encounter any difficulties or have any questions, please feel free to reach out to me. I am here to assist you throughout the process and ensure a smooth data collection experience. Your dedication and contribution will make a meaningful impact on our students' educational journeys.</p><p>Thank you,</p><p>${caseManagerName}<br>Case Manager</p>`,
});
// TODO: when site is deployed, add new url to html above
// TODO elsewhere: add "email_verified_at" timestamp when para first signs in with their email address (entered into db by cm)
}
return para;

return paraData;
// TODO: Logic for sending email to staff. Should email be sent everytime or only first time? Should staff be notified that they are added to a certain case manager's list?
// TODO: when site is deployed, add new url to html above
// TODO elsewhere: add "email_verified_at" timestamp when para first signs in with their email address (entered into db by cm)
}),

getMyTasks: authenticatedProcedure.query(async (req) => {
Expand Down
12 changes: 2 additions & 10 deletions src/pages/staff/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,7 @@ const Staff = () => {
const { data: paras, isLoading } = trpc.case_manager.getMyParas.useQuery();
const { data: me } = trpc.user.getMe.useQuery();

const createPara = trpc.para.createPara.useMutation({
onSuccess: async (data) => {
await assignParaToCaseManager.mutateAsync({
para_id: data?.user_id as string,
});
},
});

const assignParaToCaseManager = trpc.case_manager.addPara.useMutation({
const addStaff = trpc.case_manager.addStaff.useMutation({
onSuccess: () => utils.case_manager.getMyParas.invalidate(),
});

Expand All @@ -24,7 +16,7 @@ const Staff = () => {
const data = new FormData(event.currentTarget);

try {
await createPara.mutateAsync({
await addStaff.mutateAsync({
first_name: data.get("first_name") as string,
last_name: data.get("last_name") as string,
email: data.get("email") as string,
Expand Down
Loading