Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 28 additions & 27 deletions db/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,55 @@
```mermaid
erDiagram
flyway_schema_history {
integer checksum
character_varying description "{NOT_NULL}"
integer execution_time "{NOT_NULL}"
character_varying installed_by "{NOT_NULL}"
timestamp_without_time_zone installed_on "{NOT_NULL}"
integer installed_rank PK "{NOT_NULL}"
character_varying version
character_varying description "{NOT_NULL}"
character_varying type "{NOT_NULL}"
character_varying script "{NOT_NULL}"
integer checksum
character_varying installed_by "{NOT_NULL}"
timestamp installed_on "{NOT_NULL}"
integer execution_time "{NOT_NULL}"
boolean success "{NOT_NULL}"
character_varying type "{NOT_NULL}"
character_varying version
}

match_cycles {
uuid id PK "{NOT_NULL}"
integer id PK "{NOT_NULL}"
text period
timestamp_with_time_zone run_at "{NOT_NULL}"
integer total_matched
timestamptz run_at "{NOT_NULL}"
integer total_members
ARRAY unmatched_id
integer total_matched
integer[] unmatched_id
}

matches {
timestamp_with_time_zone created_at "{NOT_NULL}"
uuid cycle_id FK "{NOT_NULL}"
integer feedback_a
integer feedback_b
uuid id PK "{NOT_NULL}"
real match_score
uuid member_a_id FK "{NOT_NULL}"
uuid member_b_id FK "{NOT_NULL}"
integer cycle_id FK "{NOT_NULL}"
real match_score
text status
text feedback_a
text feedback_b
timestamptz created_at "{NOT_NULL}"
}

members {
boolean active
text bio
timestamp_with_time_zone created_at "{NOT_NULL}"
text email UK "{NOT_NULL}"
text extra_notes
text full_name "{NOT_NULL}"
uuid id PK "{NOT_NULL}"
text industry
text linked_url
text match_pref
text first_name "{NOT_NULL}"
text last_name "{NOT_NULL}"
Comment thread
Arshadul-Monir marked this conversation as resolved.
text email UK "{NOT_NULL}"
text linked_in_url
text introduction
text referral_source
text role
boolean active
text match_pref
text industry_pref
text role_pref
text topics
timestamp_with_time_zone updated_at "{NOT_NULL}"
text extra_notes
timestamptz created_at "{NOT_NULL}"
timestamptz updated_at "{NOT_NULL}"
}

matches }o--|| match_cycles : "cycle_id"
Expand Down
18 changes: 12 additions & 6 deletions js/src/features/emails/_components/CsvUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ export function UserCsvUpload({
setRows(
Array.from(users.values()).map((user) => (
<Table.Tr key={user.email}>
<Table.Td style={rowStyle}>{user.name}</Table.Td>
<Table.Td style={rowStyle}>{user.firstName}</Table.Td>
<Table.Td style={rowStyle}>{user.lastName}</Table.Td>
<Table.Td style={rowStyle}>{user.email}</Table.Td>
<Table.Td style={rowStyle}>{user.intro}</Table.Td>
<Table.Td style={rowStyle}>{user.linkedIn}</Table.Td>
Expand Down Expand Up @@ -156,7 +157,8 @@ export function UserCsvUpload({
>
<Table.Thead>
<Table.Tr>
<Table.Th>Name</Table.Th>
<Table.Th>First name</Table.Th>
<Table.Th>Last name</Table.Th>
<Table.Th>Email</Table.Th>
<Table.Th>Intro</Table.Th>
<Table.Th>Linked In</Table.Th>
Expand Down Expand Up @@ -198,9 +200,11 @@ export function PairingCsvUpload({
setRows(
Array.from(pairs.values()).map((pair) => (
<Table.Tr key={`${pair.emailA}-${pair.emailB}`}>
<Table.Td>{pair.fullNameA}</Table.Td>
<Table.Td>{pair.firstNameA}</Table.Td>
<Table.Td>{pair.lastNameA}</Table.Td>
<Table.Td>{pair.emailA}</Table.Td>
<Table.Td>{pair.fullNameB}</Table.Td>
<Table.Td>{pair.firstNameB}</Table.Td>
<Table.Td>{pair.lastNameB}</Table.Td>
<Table.Td>{pair.emailB}</Table.Td>
</Table.Tr>
)),
Expand Down Expand Up @@ -237,9 +241,11 @@ export function PairingCsvUpload({
>
<Table.Thead>
<Table.Tr>
<Table.Th>Name #1</Table.Th>
<Table.Th>First name #1</Table.Th>
<Table.Th>Last name #1</Table.Th>
<Table.Th>Email #1</Table.Th>
<Table.Th>Name #2</Table.Th>
<Table.Th>First name #2</Table.Th>
<Table.Th>Last name #2</Table.Th>
<Table.Th>Email #2</Table.Th>
</Table.Tr>
</Table.Thead>
Expand Down
12 changes: 6 additions & 6 deletions js/src/features/emails/api/emailTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* Message-level variables are referenced un-prefixed (e.g. ${pair}); per-recipient
* variables are referenced positionally as ${per.x} / ${per.x}.
*
* Note: Users are refered to as per1.name even if the email is only sent to one
* Note: Users are referred to as per1.firstName even if the email is only sent to one
* user. Also, attributes such as 'intro' is reference without the perN prefix in
* emails sent to individual users, but would be referenced with the perN prefix
* if included in a message sent to multiple users.
Expand Down Expand Up @@ -52,7 +52,7 @@ export function emailTemplate(
export const SAMPLE_TEMPLATE: EmailTemplate = emailTemplate(
"Sample",
"You're paired!",
`Hi \${per1.name} and \${per2.name}.\n\nYou're paired with \${per1.name} & \${per2.name}.`,
`Hi \${per1.firstName} \${per1.lastName} and \${per2.firstName} \${per2.lastName}.\n\nYou're paired with \${per1.firstName} & \${per2.firstName}.`,
Comment thread
Arshadul-Monir marked this conversation as resolved.
"coordinator@patinanetwork.org",
);

Expand All @@ -65,10 +65,10 @@ export const PAIR_TEMPLATE: EmailTemplate = emailTemplate(
`Hey \${per1.firstName} and \${per2.firstName}! \n
We've paired you two for PatChats this month! Find some time to have a 30 minute coffee chat or video call with your pairing!
Share a screenshot or selfie of you two in the #pat-chats channel on the Discord server! \n
\${per1.name} (\${per1.email}):
\${per1.firstName} \${per1.lastName} (\${per1.email}):
\${per1.intro: ${INTRO_MISSING}}
\${per1.linkedin:}\n
\${per2.name} (\${per2.email}):
\${per2.firstName} \${per2.lastName} (\${per2.email}):
\${per2.intro: ${INTRO_MISSING}}
\${per2.linkedin:}\n
Let me know if you'd like to update your pairing information or want to be taken off the list.\n
Expand All @@ -85,8 +85,8 @@ Just a friendly reminder that you were paired for PatChats this month!
If you haven't had your 30 minute coffee chat or video call yet, now's a great time to schedule it.
Don't forget to share a screenshot or selfie in the #pat-chats channel on the Discord server!

\${per1.name} (\${per1.email})
\${per2.name} (\${per2.email})
\${per1.firstName} \${per1.lastName} (\${per1.email})
\${per2.firstName} \${per2.lastName} (\${per2.email})

Let me know if you'd like to update your pairing information or be taken off the list.

Expand Down
29 changes: 17 additions & 12 deletions js/src/features/emails/api/parseCSV.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,25 @@ export const dataToSendRequest = async (
Object.entries(vars).filter(([, value]) => value?.trim()),
);

// Turn a full User record into a recipient oject with their variables.
// Turn a User record into a recipient object with their variables.
const toRecipient = (u: User) => ({
email: u.email,
variableToValue: withoutEmpty({
name: u.name,
firstName: u.firstName,
lastName: u.lastName,
email: u.email,
intro: u.intro,
linkedIn: u.linkedIn,
industry: u.industry,
preferences: u.preferences,
topics: u.topics,
anything: u.anything,
firstName: (u.name ?? "").split(" ")[0],
lastName: (u.name ?? "").split(" ").slice(1).join(" "),
}),
});

let messages;
if (pairList.length > 0) {
// One message per pair, addressed to two recipients. The pairing file only gives us names + emails,
// One message per pair, addressed to two recipients. The pairing file only gives us names and emails,
// so we look each person up in userMap to add the user with their full set of variables.
messages = pairList.map((p) => {
const userA = userMap.get(p.emailA);
Expand Down Expand Up @@ -98,7 +97,8 @@ export async function parseUserFile(
// Check User CSV for required headers
const headers = results.meta.fields ?? [];
const requiredHeaders = [
"name",
"firstName",
"lastName",
"email",
"intro",
"linkedIn",
Expand All @@ -119,10 +119,13 @@ export async function parseUserFile(
for (const userData of results.data) {
const emailValue = userData.email?.trim();
if (!emailValue) {
throw new Error(`Missing email for user: ${userData.name}`);
throw new Error(
`Missing email for user: ${userData.firstName} ${userData.lastName}`,
);
}
const user: User = {
name: userData.name,
firstName: userData.firstName,
lastName: userData.lastName,
// Trim so trailing whitespace from the CSV doesn't fail backend email validation.
email: emailValue,
intro: userData.intro,
Expand Down Expand Up @@ -160,11 +163,13 @@ export async function parsePairingFile(pairFile: File): Promise<Pair[]> {
const pairings = [];
for (const pairData of results.data) {
const pair: Pair = {
fullNameA: pairData[0],
firstNameA: pairData[0],
Comment thread
Arshadul-Monir marked this conversation as resolved.
lastNameA: pairData[1],
// Trim so emails match the trimmed userMap keys and pass backend validation.
emailA: pairData[1]?.trim(),
fullNameB: pairData[2],
emailB: pairData[3]?.trim(),
emailA: pairData[2]?.trim(),
firstNameB: pairData[3],
lastNameB: pairData[4],
emailB: pairData[5]?.trim(),
};
pairings.push(pair);
}
Expand Down
11 changes: 6 additions & 5 deletions js/src/features/emails/dto/emailDto.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
export interface User {
name: string;
firstName: string;
lastName: string;
email: string;
intro: string;
linkedIn: string;
industry: string;
preferences: string;
topics: string;
anything: string;
firstName?: string;
lastName?: string;
}

export interface Pair {
fullNameA: string;
firstNameA: string;
lastNameA: string;
emailA: string;
fullNameB: string;
firstNameB: string;
lastNameB: string;
emailB: string;
}

Expand Down
18 changes: 9 additions & 9 deletions js/src/features/emails/examples/pairings-test.csv
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Alice Smith,alice@example.com,Bob Jones,bob@example.com
AB,AB@example.com,CD,CD@example.com
EF,EF@example.com,GH,GH@example.com
IJ,IJ@example.com,KL,KL@example.com
MN,MN@example.com,OP,OP@example.com
QR,QR@example.com,ST,ST@example.com
UV,UV@example.com,WX,WX@example.com
Alice Smith, alice@example.com, YZ,YZ@example.com
Bob Jones, bob@example.com, IL,IL@example.com
Alice,Smith,alice@example.com,Bob,Jones,bob@example.com
AB,,AB@example.com,CD,,CD@example.com
EF,,EF@example.com,GH,,GH@example.com
IJ,,IJ@example.com,KL,,KL@example.com
MN,,MN@example.com,OP,,OP@example.com
QR,,QR@example.com,ST,,ST@example.com
UV,,UV@example.com,WX,,WX@example.com
Alice,Smith, alice@example.com,YZ,,YZ@example.com
Bob,Jones, bob@example.com,IL,,IL@example.com
35 changes: 17 additions & 18 deletions js/src/features/emails/examples/users-test.csv
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
timestamp,name,email,intro,linkedIn,industry,preferences,topics,anything,,,
4/14/2026 21:30:13,Alice Smith, alice@example.com,"Alice intro",https://www.linkedin.com/in/thmd,SWE,"In order (but no dealbreakers): Industry mentors, industry mentees, mentors, mentee",,,,,
4/11/2026 11:03:07,Bob Jones, bob@example.com,,,,
4/11/2026 11:03:07,AB,AB@example.com,,,,
4/11/2026 11:03:07,CD,CD@example.com,,,,
4/11/2026 11:03:07,EF,EF@example.com,,,,
4/11/2026 11:03:07,GH,GH@example.com,,,,
4/11/2026 11:03:07,IJ,IJ@example.com,,,,
4/11/2026 11:03:07,KL,KL@example.com,,,,
4/11/2026 11:03:07,MN,MN@example.com,,,,
4/11/2026 11:03:07,OP,OP@example.com,,,,
4/11/2026 11:03:07,QR,QR@example.com,,,,
4/11/2026 11:03:07,ST,ST@example.com,,,,
4/11/2026 11:03:07,UV,UV@example.com,,,,
4/11/2026 11:03:07,WX,WX@example.com,,,,
4/11/2026 11:03:07,YZ,YZ@example.com,,,,
4/11/2026 11:03:07,IL,IL@example.com,,,,

timestamp,firstName,lastName,email,intro,linkedIn,industry,preferences,topics,anything,,,
4/14/2026 21:30:13,Alice,Smith, alice@example.com,"Alice intro",https://www.linkedin.com/in/thmd,SWE,"In order (but no dealbreakers): Industry mentors, industry mentees, mentors, mentee",,,,,
4/11/2026 11:03:07,Bob,Jones, bob@example.com,,,,
4/11/2026 11:03:07,AB,,AB@example.com,,,,
4/11/2026 11:03:07,CD,,CD@example.com,,,,
4/11/2026 11:03:07,EF,,EF@example.com,,,,
4/11/2026 11:03:07,GH,,GH@example.com,,,,
4/11/2026 11:03:07,IJ,,IJ@example.com,,,,
4/11/2026 11:03:07,KL,,KL@example.com,,,,
4/11/2026 11:03:07,MN,,MN@example.com,,,,
4/11/2026 11:03:07,OP,,OP@example.com,,,,
4/11/2026 11:03:07,QR,,QR@example.com,,,,
4/11/2026 11:03:07,ST,,ST@example.com,,,,
4/11/2026 11:03:07,UV,,UV@example.com,,,,
4/11/2026 11:03:07,WX,,WX@example.com,,,,
4/11/2026 11:03:07,YZ,,YZ@example.com,,,,
4/11/2026 11:03:07,IL,,IL@example.com,,,,
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.patinanetwork.patchats.api.member;

import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.patinanetwork.patchats.api.member.db.models.Member;
Expand Down Expand Up @@ -40,6 +41,10 @@ public MemberDto createMember(CreateMemberRequest request) {
return MemberDto.from(createdMember);
}

public List<MemberDto> getMembers() {
return memberRepo.getMembers().stream().map(MemberDto::from).toList();
}

public MemberDto updateMember(UpdateMemberRequest request, UUID id) {
Member member = memberRepo.getMemberById(id).orElseThrow(() -> new MemberNotFoundException(id));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.patinanetwork.patchats.api.member.db.repos;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.patinanetwork.patchats.api.member.db.models.Member;
Expand All @@ -21,6 +22,8 @@ public interface MemberRepo {
*/
Member createMember(Member member);

List<Member> getMembers();

/**
* @note - The provided object's methods will be overridden with any returned data from the database.
* @param member - overridden fields:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -90,6 +91,12 @@ public Member createMember(Member member) {
.single();
}

@Override
public List<Member> getMembers() {
String sql = "SELECT * FROM members ORDER BY created_at DESC, id";
return jdbc.sql(sql).query((rs, rowNum) -> parseResultSetToMember(rs)).list();
}

@Override
public Optional<Member> updateMember(Member member) {
throw new UnsupportedOperationException("Not implemented yet");
Comment thread
Arshadul-Monir marked this conversation as resolved.
Expand Down
Loading
Loading